From e08cbed3c13100b912b8209c706b42e5cfbabf53 Mon Sep 17 00:00:00 2001
From: Henri Chataing <henrichataing@google.com>
Date: Fri, 20 Oct 2023 09:06:51 -0700
Subject: [PATCH] system: fmtlib logger implementation

- The fmtlib logger is implemented in <bluetooth/log.h>.
  The header defines the following templated logs function:

    template<typename T...>
    log::fatal(fmt::format_string<T...> fmt, T...args);
    log::error(..);
    log::warn(..);
    log::info(..);
    log::debug(..);
    log::verbose(..);

- Front-end, logs are printed out by invoking these
  macros with the macro LOG_TAG defined _before_ the
  inclusion of #include <bluetooth/log.h>

- Back-end, a single method must be implemented for all supported
  platforms (android, floss, host):

  namespace log_internal {
  void vlog(Level level, char const *tag, char const *file_name, int line,
            fmt::string_view fmt, fmt::format_args vargs);
  }

- Default implementations are provided:
  + vlog_android: outputs to <log/log.h> __android_log_write_log_message
  + vlog_syslog: outputs to <syslog.h> syslog

Bug: 305066880
Test: m libbluetooth_log
Test: atest libbluetooth_log_test
Flag: EXEMPT, logging utils
Change-Id: Ic8a80f113b25d874c372d7dce8252d5428842ee8
---
 README.md                                     |   2 +-
 build.py                                      |   1 +
 floss/build/Dockerfile                        |   1 +
 system/BUILD.gn                               |   8 +
 system/build/dpkg/floss/build-dpkg            |   2 +-
 system/build/dpkg/floss/install-dependencies  |   2 +-
 .../build/dpkg/floss/package/DEBIAN/control   |   2 +-
 system/log/Android.bp                         |  31 ++++
 system/log/BUILD.gn                           |  36 +++++
 system/log/include/bluetooth/log.h            | 146 ++++++++++++++++++
 system/log/src/truncating_buffer.h            |  69 +++++++++
 system/log/src/truncating_buffer_test.cc      |  83 ++++++++++
 system/log/src/vlog_android.cc                |  53 +++++++
 system/log/src/vlog_syslog.cc                 |  66 ++++++++
 system/log/src/vlog_test.cc                   | 114 ++++++++++++++
 15 files changed, 612 insertions(+), 4 deletions(-)
 create mode 100644 system/log/Android.bp
 create mode 100644 system/log/BUILD.gn
 create mode 100644 system/log/include/bluetooth/log.h
 create mode 100644 system/log/src/truncating_buffer.h
 create mode 100644 system/log/src/truncating_buffer_test.cc
 create mode 100644 system/log/src/vlog_android.cc
 create mode 100644 system/log/src/vlog_syslog.cc
 create mode 100644 system/log/src/vlog_test.cc

diff --git a/README.md b/README.md
index 60daa85761d..3df5e04b6d5 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ sudo apt-get install repo git-core gnupg flex bison gperf build-essential \
   libgl1-mesa-dev libxml2-utils xsltproc unzip liblz4-tool libssl-dev \
   libc++-dev libevent-dev \
   flatbuffers-compiler libflatbuffers1 openssl \
-  libflatbuffers-dev libtinyxml2-dev \
+  libflatbuffers-dev libfmt-dev libtinyxml2-dev \
   libglib2.0-dev libevent-dev libnss3-dev libdbus-1-dev \
   libprotobuf-dev ninja-build generate-ninja protobuf-compiler \
   libre2-9 debmake \
diff --git a/build.py b/build.py
index 5e03c6388c7..d47005a41f2 100755
--- a/build.py
+++ b/build.py
@@ -127,6 +127,7 @@ REQUIRED_APT_PACKAGES = [
     'liblz4-tool',
     'libncurses5',
     'libnss3-dev',
+    'libfmt-dev',
     'libprotobuf-dev',
     'libre2-9',
     'libre2-dev',
diff --git a/floss/build/Dockerfile b/floss/build/Dockerfile
index e0e48248564..2be32777732 100644
--- a/floss/build/Dockerfile
+++ b/floss/build/Dockerfile
@@ -31,6 +31,7 @@ RUN apt-get update && \
     libdouble-conversion-dev \
     libevent-dev \
     libflatbuffers-dev \
+    libfmt-dev \
     libgl1-mesa-dev \
     libglib2.0-dev \
     libgtest-dev \
diff --git a/system/BUILD.gn b/system/BUILD.gn
index 02b92d245b2..62ced34597e 100644
--- a/system/BUILD.gn
+++ b/system/BUILD.gn
@@ -174,6 +174,10 @@ config("external_flatbuffers") {
   libs = [ "flatbuffers" ]
 }
 
+config("external_fmtlib") {
+  configs = [ ":pkg_fmtlib" ]
+}
+
 # Package configurations to extract dependencies from env
 pkg_config("pkg_gtest") {
   pkg_deps = [ "gtest" ]
@@ -203,6 +207,10 @@ pkg_config("pkg_tinyxml2") {
   pkg_deps = [ "tinyxml2" ]
 }
 
+pkg_config("pkd_fmtlib") {
+  pkg_deps = [ "fmt" ]
+}
+
 # To include ChroemOS-specific libraries and build dependencies.
 if (target_os == "chromeos") {
   config("external_chromeos") {
diff --git a/system/build/dpkg/floss/build-dpkg b/system/build/dpkg/floss/build-dpkg
index e9cb70b9feb..fececbc6895 100755
--- a/system/build/dpkg/floss/build-dpkg
+++ b/system/build/dpkg/floss/build-dpkg
@@ -48,7 +48,7 @@ export PATH="${PATH}:${BIN_DIR}"
 
 # Check dependencies
 # libchrome requires modp_b64
-APT_REQUIRED="modp-b64 libchrome flatbuffers-compiler flex g++-multilib gcc-multilib generate-ninja gnupg gperf libc++-dev libdbus-1-dev libevent-dev libevent-dev libflatbuffers-dev libflatbuffers1 libgl1-mesa-dev libglib2.0-dev liblz4-tool libncurses5 libnss3-dev libprotobuf-dev libre2-9 libssl-dev libtinyxml2-dev libx11-dev libxml2-utils ninja-build openssl protobuf-compiler unzip x11proto-core-dev xsltproc zip zlib1g-dev"
+APT_REQUIRED="modp-b64 libchrome flatbuffers-compiler flex g++-multilib gcc-multilib generate-ninja gnupg gperf libc++-dev libdbus-1-dev libevent-dev libevent-dev libflatbuffers-dev libflatbuffers1 libfmt-dev libgl1-mesa-dev libglib2.0-dev liblz4-tool libncurses5 libnss3-dev libprotobuf-dev libre2-9 libssl-dev libtinyxml2-dev libx11-dev libxml2-utils ninja-build openssl protobuf-compiler unzip x11proto-core-dev xsltproc zip zlib1g-dev"
 
 # SPEED UP TEST, REMOVE ME
 APT_REQUIRED="modp-b64 libchrome flatbuffers-compiler"
diff --git a/system/build/dpkg/floss/install-dependencies b/system/build/dpkg/floss/install-dependencies
index a1b33a74a66..e215399f6fa 100755
--- a/system/build/dpkg/floss/install-dependencies
+++ b/system/build/dpkg/floss/install-dependencies
@@ -24,7 +24,7 @@ function ctrl_c() {
 
 # APT dependencies
 APT_REQUIRED="git curl wget flatbuffers-compiler flex g++-multilib gcc-multilib generate-ninja \
-gnupg gperf libc++-dev libdbus-1-dev libevent-dev libflatbuffers-dev libflatbuffers1 \
+gnupg gperf libc++-dev libdbus-1-dev libevent-dev libflatbuffers-dev libfmt-dev libflatbuffers1 \
 libgl1-mesa-dev libglib2.0-dev liblz4-tool libncurses5 libnss3-dev libprotobuf-dev libre2-9 \
 libssl-dev libtinyxml2-dev libx11-dev libxml2-utils ninja-build openssl protobuf-compiler unzip \
 x11proto-core-dev xsltproc zip zlib1g-dev libc++abi-dev cmake debmake ninja-build libgtest-dev \
diff --git a/system/build/dpkg/floss/package/DEBIAN/control b/system/build/dpkg/floss/package/DEBIAN/control
index 3bb5d35eb9f..d5f4520c6cf 100644
--- a/system/build/dpkg/floss/package/DEBIAN/control
+++ b/system/build/dpkg/floss/package/DEBIAN/control
@@ -4,7 +4,7 @@ Priority: optional
 Maintainer: Martin Brabham <optedoblivion@google.com>
 Version: 0.1
 Homepage: https://www.google.com
-Depends: debmake, ninja-build, flatbuffers-compiler, flex, g++-multilib, gcc-multilib, generate-ninja, gnupg, gperf, libc++-dev, libdbus-1-dev, libevent-dev, libevent-dev, libflatbuffers-dev, libflatbuffers1, libgl1-mesa-dev, libglib2.0-dev, liblz4-tool, libncurses5, libnss3-dev, libprotobuf-dev, libre2-9, libssl-dev, libtinyxml2-dev, libx11-dev, libxml2-utils, ninja-build, openssl, protobuf-compiler, unzip, x11proto-core-dev, xsltproc, zip, zlib1g-dev, modp-b64, libchrome
+Depends: debmake, ninja-build, flatbuffers-compiler, flex, g++-multilib, gcc-multilib, generate-ninja, gnupg, gperf, libc++-dev, libdbus-1-dev, libevent-dev, libevent-dev, libflatbuffers-dev, libflatbuffers1, libfmt-dev, libfmt9, libgl1-mesa-dev, libglib2.0-dev, liblz4-tool, libncurses5, libnss3-dev, libprotobuf-dev, libre2-9, libssl-dev, libtinyxml2-dev, libx11-dev, libxml2-utils, ninja-build, openssl, protobuf-compiler, unzip, x11proto-core-dev, xsltproc, zip, zlib1g-dev, modp-b64, libchrome
 Architecture: all
 Essential: no
 Installed-Size: 490MB
diff --git a/system/log/Android.bp b/system/log/Android.bp
new file mode 100644
index 00000000000..10c7e730db8
--- /dev/null
+++ b/system/log/Android.bp
@@ -0,0 +1,31 @@
+cc_library {
+    name: "libbluetooth_log",
+    host_supported: true,
+    min_sdk_version: "33",
+    apex_available: [
+        "com.android.btservices",
+    ],
+    export_include_dirs: [
+        "include",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    srcs: [
+        "src/vlog_android.cc",
+    ],
+}
+
+cc_test {
+    name: "libbluetooth_log_test",
+    host_supported: true,
+    srcs: [
+        "src/truncating_buffer_test.cc",
+        "src/vlog_test.cc",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbluetooth_log",
+    ],
+}
diff --git a/system/log/BUILD.gn b/system/log/BUILD.gn
new file mode 100644
index 00000000000..b7b94e7deed
--- /dev/null
+++ b/system/log/BUILD.gn
@@ -0,0 +1,36 @@
+#
+#  Copyright 2024 Google, Inc.
+#
+#  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:
+#
+#  http://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("log_defaults") {
+  include_dirs = [
+    "//bt/system/log/include",
+  ]
+}
+
+static_library("libbluetooth_log") {
+  cflags = [
+     "-fvisibility=default",
+  ]
+  sources = [
+    "include/bluetooth/log.h",
+    "src/truncating_buffer.h",
+    "src/vlog_syslog.cc",
+  ]
+  configs += [
+    "//bt/system:target_defaults",
+    ":log_defaults",
+  ]
+}
diff --git a/system/log/include/bluetooth/log.h b/system/log/include/bluetooth/log.h
new file mode 100644
index 00000000000..3ed77aa7a44
--- /dev/null
+++ b/system/log/include/bluetooth/log.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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 <fmt/core.h>
+#include <fmt/format.h>
+#include <fmt/std.h>
+
+#ifndef LOG_TAG
+#define LOG_TAG "bluetooth"
+#endif  // LOG_TAG
+
+namespace bluetooth::log_internal {
+
+/// Android framework log priority levels.
+/// They are defined in system/logging/liblog/include/android/log.h by
+/// the Android Framework code.
+enum Level {
+  kVerbose = 2,
+  kDebug = 3,
+  kInfo = 4,
+  kWarn = 5,
+  kError = 6,
+  kFatal = 7,
+};
+
+/// Write a single log line.
+/// The implementation of this function is dependent on the backend.
+void vlog(Level level, char const* tag, char const* file_name, int line,
+          char const* function_name, fmt::string_view fmt,
+          fmt::format_args vargs);
+
+template <Level level, typename... T>
+struct log {
+  log(fmt::format_string<T...> fmt, T&&... args,
+      char const* file_name = __builtin_FILE(), int line = __builtin_LINE(),
+      char const* function_name = __builtin_FUNCTION()) {
+    vlog(level, LOG_TAG, file_name, line, function_name,
+         static_cast<fmt::string_view>(fmt), fmt::make_format_args(args...));
+  }
+};
+
+#if (__cplusplus >= 202002L && defined(__GNUC__) && !defined(__clang__))
+
+template <int level, typename... T>
+log(fmt::format_string<T...>, T&&...) -> log<level, T...>;
+
+#endif
+
+}  // namespace bluetooth::log_internal
+
+namespace bluetooth::log {
+
+#if (__cplusplus >= 202002L && defined(__GNUC__) && !defined(__clang__))
+
+template <typename... T>
+using fatal = log_internal::log<log_internal::kFatal, T...>;
+template <typename... T>
+using error = log_internal::log<log_internal::kError, T...>;
+template <typename... T>
+using warning = log_internal::log<log_internal::kWarning, T...>;
+template <typename... T>
+using info = log_internal::log<log_internal::kInfo, T...>;
+template <typename... T>
+using debug = log_internal::log<log_internal::kDebug, T...>;
+template <typename... T>
+using verbose = log_internal::log<log_internal::kVerbose, T...>;
+
+#else
+
+template <typename... T>
+struct fatal : log_internal::log<log_internal::kFatal, T...> {
+  using log_internal::log<log_internal::kFatal, T...>::log;
+};
+template <typename... T>
+struct error : log_internal::log<log_internal::kError, T...> {
+  using log_internal::log<log_internal::kError, T...>::log;
+};
+template <typename... T>
+struct warn : log_internal::log<log_internal::kWarn, T...> {
+  using log_internal::log<log_internal::kWarn, T...>::log;
+};
+template <typename... T>
+struct info : log_internal::log<log_internal::kInfo, T...> {
+  using log_internal::log<log_internal::kInfo, T...>::log;
+};
+template <typename... T>
+struct debug : log_internal::log<log_internal::kDebug, T...> {
+  using log_internal::log<log_internal::kDebug, T...>::log;
+};
+template <typename... T>
+struct verbose : log_internal::log<log_internal::kVerbose, T...> {
+  using log_internal::log<log_internal::kVerbose, T...>::log;
+};
+
+template <typename... T>
+fatal(fmt::format_string<T...>, T&&...) -> fatal<T...>;
+template <typename... T>
+error(fmt::format_string<T...>, T&&...) -> error<T...>;
+template <typename... T>
+warn(fmt::format_string<T...>, T&&...) -> warn<T...>;
+template <typename... T>
+info(fmt::format_string<T...>, T&&...) -> info<T...>;
+template <typename... T>
+debug(fmt::format_string<T...>, T&&...) -> debug<T...>;
+template <typename... T>
+verbose(fmt::format_string<T...>, T&&...) -> verbose<T...>;
+
+#endif  // GCC / C++20
+
+}  // namespace bluetooth::log
+
+namespace fmt {
+
+/// Default formatter implementation for formatting
+/// enum class values to the underlying type.
+///
+/// Enable this formatter in the code by declaring:
+/// ```
+/// template<>
+/// struct fmt::formatter<EnumT> : enum_formatter<EnumT> {};
+/// ```
+template <typename EnumT, class CharT = char>
+struct enum_formatter : fmt::formatter<std::underlying_type_t<EnumT>, CharT> {
+  template <class Context>
+  typename Context::iterator format(EnumT value, Context& ctx) const {
+    return fmt::formatter<std::underlying_type_t<EnumT>, CharT>::format(
+        static_cast<std::underlying_type_t<EnumT>>(value), ctx);
+  }
+};
+
+}  // namespace fmt
diff --git a/system/log/src/truncating_buffer.h b/system/log/src/truncating_buffer.h
new file mode 100644
index 00000000000..c2ad0014a95
--- /dev/null
+++ b/system/log/src/truncating_buffer.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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 <cstddef>
+
+namespace bluetooth::log_internal {
+
+/// Truncating write buffer.
+///
+/// This buffer can be used with `std::back_insert_iterator` to create
+/// an output iterator. All write actions beyond the maximum length of
+/// the buffer are silently ignored.
+template <int buffer_size>
+struct truncating_buffer {
+  using value_type = char;
+
+  void push_back(char c) {
+    if (len < buffer_size - 1) {
+      buffer[len++] = c;
+    }
+  }
+
+  char const* c_str() {
+    if (len == buffer_size - 1) {
+      // Inspect the last 4 bytes of the buffer to check if
+      // the last character was truncated. Remove the character
+      // entirely if that's the case.
+      for (size_t n = 0; n < 4; n++) {
+        char c = buffer[len - n - 1];
+        if ((c & 0b11000000) == 0b10000000) {
+          continue;
+        }
+        size_t char_len = (c & 0b10000000) == 0b00000000   ? 1
+                          : (c & 0b11100000) == 0b11000000 ? 2
+                          : (c & 0b11110000) == 0b11100000 ? 3
+                          : (c & 0b11111000) == 0b11110000 ? 4
+                                                           : 0;
+        if ((n + 1) < char_len) {
+          len -= n + 1;
+        }
+        break;
+      }
+    }
+
+    buffer[len] = '\0';
+    return buffer;
+  }
+
+ private:
+  char buffer[buffer_size];
+  size_t len{0};
+};
+
+}  // namespace bluetooth::log_internal
diff --git a/system/log/src/truncating_buffer_test.cc b/system/log/src/truncating_buffer_test.cc
new file mode 100644
index 00000000000..5790270cdc6
--- /dev/null
+++ b/system/log/src/truncating_buffer_test.cc
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#define LOG_TAG "test"
+
+#include "truncating_buffer.h"
+
+#include <fmt/format.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+using namespace bluetooth::log_internal;
+
+TEST(TruncatingBufferTest, 1byte) {
+  EXPECT_EQ(sizeof("ab"), 3);
+  truncating_buffer<2> buffer_1;
+  truncating_buffer<3> buffer_2;
+  fmt::format_to(std::back_insert_iterator(buffer_1), "ab");
+  fmt::format_to(std::back_insert_iterator(buffer_2), "ab");
+  EXPECT_STREQ(buffer_1.c_str(), "a");
+  EXPECT_STREQ(buffer_2.c_str(), "ab");
+}
+
+TEST(TruncatingBufferTest, 2bytes) {
+  EXPECT_EQ(sizeof("αβ"), 5);
+  truncating_buffer<3> buffer_1;
+  truncating_buffer<4> buffer_2;
+  truncating_buffer<5> buffer_3;
+  fmt::format_to(std::back_insert_iterator(buffer_1), "αβ");
+  fmt::format_to(std::back_insert_iterator(buffer_2), "αβ");
+  fmt::format_to(std::back_insert_iterator(buffer_3), "αβ");
+  EXPECT_STREQ(buffer_1.c_str(), "α");
+  EXPECT_STREQ(buffer_2.c_str(), "α");
+  EXPECT_STREQ(buffer_3.c_str(), "αβ");
+}
+
+TEST(TruncatingBufferTest, 3bytes) {
+  EXPECT_EQ(sizeof("ພຮ"), 7);
+  truncating_buffer<4> buffer_1;
+  truncating_buffer<5> buffer_2;
+  truncating_buffer<6> buffer_3;
+  truncating_buffer<7> buffer_4;
+  fmt::format_to(std::back_insert_iterator(buffer_1), "ພຮ");
+  fmt::format_to(std::back_insert_iterator(buffer_2), "ພຮ");
+  fmt::format_to(std::back_insert_iterator(buffer_3), "ພຮ");
+  fmt::format_to(std::back_insert_iterator(buffer_4), "ພຮ");
+  EXPECT_STREQ(buffer_1.c_str(), "ພ");
+  EXPECT_STREQ(buffer_2.c_str(), "ພ");
+  EXPECT_STREQ(buffer_3.c_str(), "ພ");
+  EXPECT_STREQ(buffer_4.c_str(), "ພຮ");
+}
+
+TEST(TruncatingBufferTest, 4bytes) {
+  EXPECT_EQ(sizeof("𐎡𐎪"), 9);
+  truncating_buffer<5> buffer_1;
+  truncating_buffer<6> buffer_2;
+  truncating_buffer<7> buffer_3;
+  truncating_buffer<8> buffer_4;
+  truncating_buffer<9> buffer_5;
+  fmt::format_to(std::back_insert_iterator(buffer_1), "𐎡𐎪");
+  fmt::format_to(std::back_insert_iterator(buffer_2), "𐎡𐎪");
+  fmt::format_to(std::back_insert_iterator(buffer_3), "𐎡𐎪");
+  fmt::format_to(std::back_insert_iterator(buffer_4), "𐎡𐎪");
+  fmt::format_to(std::back_insert_iterator(buffer_5), "𐎡𐎪");
+  EXPECT_STREQ(buffer_1.c_str(), "𐎡");
+  EXPECT_STREQ(buffer_2.c_str(), "𐎡");
+  EXPECT_STREQ(buffer_3.c_str(), "𐎡");
+  EXPECT_STREQ(buffer_4.c_str(), "𐎡");
+  EXPECT_STREQ(buffer_5.c_str(), "𐎡𐎪");
+}
diff --git a/system/log/src/vlog_android.cc b/system/log/src/vlog_android.cc
new file mode 100644
index 00000000000..38930ca4291
--- /dev/null
+++ b/system/log/src/vlog_android.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#include <log/log.h>
+
+#include "bluetooth/log.h"
+#include "truncating_buffer.h"
+
+namespace bluetooth::log_internal {
+
+static constexpr size_t kBufferSize = 1024;
+
+void vlog(Level level, char const* tag, char const* file_name, int line,
+          char const* function_name, fmt::string_view fmt,
+          fmt::format_args vargs) {
+  // Check if log is enabled.
+  if (!__android_log_is_loggable(level, tag, ANDROID_LOG_DEFAULT) &&
+      !__android_log_is_loggable(level, "bluetooth", ANDROID_LOG_DEFAULT)) {
+    return;
+  }
+
+  // Format to stack buffer.
+  truncating_buffer<kBufferSize> buffer;
+  fmt::format_to(std::back_insert_iterator(buffer), "{}: ", function_name);
+  fmt::vformat_to(std::back_insert_iterator(buffer), fmt, vargs);
+
+  // Send message to liblog.
+  struct __android_log_message message = {
+      .struct_size = sizeof(__android_log_message),
+      .buffer_id = LOG_ID_MAIN,
+      .priority = static_cast<android_LogPriority>(level),
+      .tag = tag,
+      .file = file_name,
+      .line = static_cast<uint32_t>(line),
+      .message = buffer.c_str(),
+  };
+  __android_log_write_log_message(&message);
+}
+
+}  // namespace bluetooth::log_internal
diff --git a/system/log/src/vlog_syslog.cc b/system/log/src/vlog_syslog.cc
new file mode 100644
index 00000000000..9d7dad1a965
--- /dev/null
+++ b/system/log/src/vlog_syslog.cc
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#include <syslog.h>
+
+#include "bluetooth/log.h"
+#include "truncating_buffer.h"
+
+namespace bluetooth::log_internal {
+
+// Default value for $MaxMessageSize for rsyslog.
+static constexpr size_t kBufferSize = 8192;
+
+void vlog(Level level, char const* tag, char const* file_name, int line,
+          char const* function_name, fmt::string_view fmt,
+          fmt::format_args vargs) {
+  // Convert the level to syslog severity.
+  int severity = LOG_DEBUG;
+  switch (level) {
+    case Level::kVerbose:
+    case Level::kDebug:
+    default:
+      severity = LOG_DEBUG;
+      break;
+    case Level::kInfo:
+      severity = LOG_INFO;
+      break;
+    case Level::kWarn:
+      severity = LOG_WARNING;
+      break;
+    case Level::kError:
+      severity = LOG_ERR;
+      break;
+    case Level::kFatal:
+      severity = LOG_CRIT;
+      break;
+  }
+
+  // Prepare bounded stack buffer.
+  truncating_buffer<kBufferSize> buffer;
+
+  // Format file, line.
+  fmt::format_to(std::back_insert_iterator(buffer), "{} {}:{} {}: ", tag,
+                 file_name, line, function_name);
+
+  // Format message.
+  fmt::vformat_to(std::back_insert_iterator(buffer), fmt, vargs);
+
+  // Print to vsyslog.
+  syslog(LOG_USER | severity, "%s", buffer.c_str());
+}
+
+}  // namespace bluetooth::log_internal
diff --git a/system/log/src/vlog_test.cc b/system/log/src/vlog_test.cc
new file mode 100644
index 00000000000..026dd737081
--- /dev/null
+++ b/system/log/src/vlog_test.cc
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#define LOG_TAG "test"
+
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#include "bluetooth/log.h"
+#include "truncating_buffer.h"
+
+/// Captures the latest message generated by the android vlog
+/// implementation.
+static std::optional<__android_log_message> androidLogMessage;
+
+/// Mask the implementation from liblog.
+int __android_log_is_loggable(int /*prio*/, const char* /*tag*/,
+                              int /*default_prio*/) {
+  return true;
+}
+
+/// Mask the implementation from liblog.
+void __android_log_write_log_message(
+    struct __android_log_message* log_message) {
+  if (log_message != nullptr) {
+    log_message->message = strdup(log_message->message);
+    androidLogMessage.emplace(*log_message);
+  }
+}
+
+using namespace bluetooth;
+
+TEST(BluetoothLoggerTest, verbose) {
+  androidLogMessage.reset();
+
+  log::verbose("verbose test");
+
+  ASSERT_TRUE(androidLogMessage.has_value());
+  EXPECT_EQ(androidLogMessage->priority, ANDROID_LOG_VERBOSE);
+  EXPECT_STREQ(androidLogMessage->tag, LOG_TAG);
+  EXPECT_STREQ(androidLogMessage->file,
+               "packages/modules/Bluetooth/system/log/src/vlog_test.cc");
+  EXPECT_EQ(androidLogMessage->line, 49);
+  EXPECT_STREQ(androidLogMessage->message, "TestBody: verbose test");
+}
+
+TEST(BluetoothLoggerTest, debug) {
+  androidLogMessage.reset();
+
+  log::debug("debug test");
+
+  ASSERT_TRUE(androidLogMessage.has_value());
+  EXPECT_EQ(androidLogMessage->priority, ANDROID_LOG_DEBUG);
+  EXPECT_STREQ(androidLogMessage->tag, LOG_TAG);
+  EXPECT_STREQ(androidLogMessage->file,
+               "packages/modules/Bluetooth/system/log/src/vlog_test.cc");
+  EXPECT_EQ(androidLogMessage->line, 63);
+  EXPECT_STREQ(androidLogMessage->message, "TestBody: debug test");
+}
+
+TEST(BluetoothLoggerTest, info) {
+  androidLogMessage.reset();
+
+  log::info("info test");
+
+  ASSERT_TRUE(androidLogMessage.has_value());
+  EXPECT_EQ(androidLogMessage->priority, ANDROID_LOG_INFO);
+  EXPECT_STREQ(androidLogMessage->tag, LOG_TAG);
+  EXPECT_STREQ(androidLogMessage->file,
+               "packages/modules/Bluetooth/system/log/src/vlog_test.cc");
+  EXPECT_EQ(androidLogMessage->line, 77);
+  EXPECT_STREQ(androidLogMessage->message, "TestBody: info test");
+}
+
+TEST(BluetoothLoggerTest, warn) {
+  androidLogMessage.reset();
+
+  log::warn("warn test");
+
+  ASSERT_TRUE(androidLogMessage.has_value());
+  EXPECT_EQ(androidLogMessage->priority, ANDROID_LOG_WARN);
+  EXPECT_STREQ(androidLogMessage->tag, LOG_TAG);
+  EXPECT_STREQ(androidLogMessage->file,
+               "packages/modules/Bluetooth/system/log/src/vlog_test.cc");
+  EXPECT_EQ(androidLogMessage->line, 91);
+  EXPECT_STREQ(androidLogMessage->message, "TestBody: warn test");
+}
+
+TEST(BluetoothLoggerTest, error) {
+  androidLogMessage.reset();
+
+  log::error("error test");
+
+  ASSERT_TRUE(androidLogMessage.has_value());
+  EXPECT_EQ(androidLogMessage->priority, ANDROID_LOG_ERROR);
+  EXPECT_STREQ(androidLogMessage->tag, LOG_TAG);
+  EXPECT_STREQ(androidLogMessage->file,
+               "packages/modules/Bluetooth/system/log/src/vlog_test.cc");
+  EXPECT_EQ(androidLogMessage->line, 105);
+  EXPECT_STREQ(androidLogMessage->message, "TestBody: error test");
+}
-- 
GitLab