diff options
62 files changed, 12367 insertions, 0 deletions
diff --git a/absl/CMakeLists.txt b/absl/CMakeLists.txt index b1715846..925be19b 100644 --- a/absl/CMakeLists.txt +++ b/absl/CMakeLists.txt @@ -22,6 +22,7 @@ add_subdirectory(debugging) add_subdirectory(flags) add_subdirectory(functional) add_subdirectory(hash) +add_subdirectory(log) add_subdirectory(memory) add_subdirectory(meta) add_subdirectory(numeric) diff --git a/absl/flags/BUILD.bazel b/absl/flags/BUILD.bazel index 4ca687ee..935d8539 100644 --- a/absl/flags/BUILD.bazel +++ b/absl/flags/BUILD.bazel @@ -53,6 +53,7 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS, visibility = [ "//absl/flags:__pkg__", + "//absl/log:__pkg__", ], deps = [ ":path_util", diff --git a/absl/log/BUILD.bazel b/absl/log/BUILD.bazel new file mode 100644 index 00000000..2a643c72 --- /dev/null +++ b/absl/log/BUILD.bazel @@ -0,0 +1,476 @@ +# +# Copyright 2022 The Abseil 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. +# + +load( + "//absl:copts/configure_copts.bzl", + "ABSL_DEFAULT_COPTS", + "ABSL_DEFAULT_LINKOPTS", + "ABSL_TEST_COPTS", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +# Public targets +cc_library( + name = "check", + hdrs = ["check.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:core_headers", + "//absl/log/internal:check_op", + "//absl/log/internal:conditions", + "//absl/log/internal:log_message", + "//absl/log/internal:strip", + ], +) + +cc_library( + name = "die_if_null", + srcs = ["die_if_null.cc"], + hdrs = ["die_if_null.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/strings", + ], +) + +cc_library( + name = "flags", + srcs = ["flags.cc"], + hdrs = ["flags.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + ":globals", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/flags:flag", + "//absl/flags:marshalling", + "//absl/log/internal:config", + "//absl/log/internal:flags", + "//absl/strings", + ], + # Binaries which do not access these flags from C++ still want this library linked in. + alwayslink = True, +) + +cc_library( + name = "globals", + srcs = ["globals.cc"], + hdrs = ["globals.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + "//absl/base:atomic_hook", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/hash", + "//absl/strings", + ], +) + +cc_library( + name = "initialize", + srcs = ["initialize.cc"], + hdrs = ["initialize.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + ":globals", + "//absl/base:config", + "//absl/log/internal:globals", + "//absl/time", + ], +) + +cc_library( + name = "log", + hdrs = ["log.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/log/internal:conditions", + "//absl/log/internal:log_message", + "//absl/log/internal:strip", + ], +) + +cc_library( + name = "log_entry", + srcs = ["log_entry.cc"], + hdrs = ["log_entry.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/base:log_severity", + "//absl/log/internal:config", + "//absl/strings", + "//absl/time", + "//absl/types:span", + ], +) + +cc_library( + name = "log_sink", + srcs = ["log_sink.cc"], + hdrs = ["log_sink.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log_entry", + "//absl/base:config", + ], +) + +cc_library( + name = "log_sink_registry", + hdrs = ["log_sink_registry.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log_sink", + "//absl/base:config", + "//absl/log/internal:log_sink_set", + ], +) + +cc_library( + name = "log_streamer", + hdrs = ["log_streamer.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + "//absl/base:config", + "//absl/base:log_severity", + "//absl/memory", + "//absl/strings", + "//absl/strings:internal", + ], +) + +cc_library( + name = "scoped_mock_log", + testonly = True, + srcs = ["scoped_mock_log.cc"], + hdrs = ["scoped_mock_log.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log_entry", + ":log_sink", + ":log_sink_registry", + "//absl/base:config", + "//absl/base:log_severity", + "//absl/base:raw_logging_internal", + "//absl/strings", + "@com_google_googletest//:gtest", + ], +) + +# Test targets +cc_test( + name = "basic_log_test", + size = "small", + srcs = ["basic_log_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":globals", + ":log", + ":log_entry", + ":scoped_mock_log", + "//absl/base", + "//absl/base:log_severity", + "//absl/log/internal:test_actions", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "check_test", + size = "small", + srcs = ["check_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "no_test_ios", + "no_test_wasm", + ], + deps = [ + ":check", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/log/internal:test_helpers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "die_if_null_test", + size = "small", + srcs = ["die_if_null_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":die_if_null", + "//absl/base:core_headers", + "//absl/log/internal:test_helpers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "flags_test", + size = "small", + srcs = ["flags_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":flags", + ":globals", + ":log", + ":scoped_mock_log", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/flags:flag", + "//absl/flags:reflection", + "//absl/log/internal:flags", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "globals_test", + size = "small", + srcs = ["globals_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":globals", + ":log", + ":scoped_mock_log", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/log/internal:globals", + "//absl/log/internal:test_helpers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "log_entry_test", + size = "small", + srcs = ["log_entry_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log_entry", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/log/internal:format", + "//absl/log/internal:test_helpers", + "//absl/strings", + "//absl/time", + "//absl/types:span", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "log_format_test", + size = "small", + srcs = ["log_format_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + ":scoped_mock_log", + "//absl/log/internal:config", + "//absl/log/internal:test_matchers", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "log_macro_hygiene_test", + size = "small", + srcs = ["log_macro_hygiene_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + ":scoped_mock_log", + "//absl/base:core_headers", + "//absl/base:log_severity", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "log_sink_test", + size = "medium", + srcs = ["log_sink_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "no_test_ios", + "no_test_wasm", + ], + deps = [ + ":log", + ":log_sink", + ":log_sink_registry", + ":scoped_mock_log", + "//absl/base:core_headers", + "//absl/base:raw_logging_internal", + "//absl/log/internal:test_actions", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "log_streamer_test", + size = "medium", + srcs = ["log_streamer_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + ":log_streamer", + ":scoped_mock_log", + "//absl/base", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/log/internal:test_actions", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "log_modifier_methods_test", + size = "small", + srcs = ["log_modifier_methods_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + ":log_sink", + ":scoped_mock_log", + "//absl/log/internal:test_actions", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "//absl/strings", + "//absl/time", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "scoped_mock_log_test", + size = "small", + srcs = ["scoped_mock_log_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + linkstatic = 1, + tags = [ + "no_test_ios", + "no_test_wasm", + ], + deps = [ + ":globals", + ":log", + ":scoped_mock_log", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "//absl/memory", + "//absl/strings", + "//absl/synchronization", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "stripping_test", + size = "small", + srcs = ["stripping_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + # This test requires all code live in the binary (instead of shared libraries) + # because we test for the existence of specific literals in the binary. + linkstatic = 1, + deps = [ + ":check", + ":log", + "//absl/base:strerror", + "//absl/flags:program_name", + "//absl/log/internal:test_helpers", + "//absl/strings", + "//absl/strings:str_format", + "@com_google_googletest//:gtest_main", + ], +) + +cc_binary( + name = "log_benchmark", + testonly = 1, + srcs = ["log_benchmark.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = ["benchmark"], + deps = [ + ":check", + ":flags", + ":globals", + ":log", + ":log_entry", + ":log_sink", + ":log_sink_registry", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/flags:flag", + "//absl/log/internal:flags", + "@com_github_google_benchmark//:benchmark_main", + ], +) diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt new file mode 100644 index 00000000..a96e5195 --- /dev/null +++ b/absl/log/CMakeLists.txt @@ -0,0 +1,836 @@ +# +# Copyright 2022 The Abseil 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. +# + +# Internal targets +absl_cc_library( + NAME + log_internal_check_op + SRCS + "internal/check_op.cc" + HDRS + "internal/check_op.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_internal_nullguard + absl::log_internal_nullstream + absl::log_internal_strip + absl::strings +) + +absl_cc_library( + NAME + log_internal_conditions + SRCS + "internal/conditions.cc" + HDRS + "internal/conditions.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::base + absl::config + absl::core_headers + absl::log_internal_voidify +) + +absl_cc_library( + NAME + log_internal_config + SRCS + HDRS + "internal/config.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers +) + +absl_cc_library( + NAME + log_internal_flags + SRCS + HDRS + "internal/flags.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::flags +) + +absl_cc_library( + NAME + log_internal_format + SRCS + "internal/log_format.cc" + HDRS + "internal/log_format.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_internal_config + absl::log_internal_globals + absl::log_severity + absl::strings + absl::str_format + absl::time + absl::span +) + +absl_cc_library( + NAME + log_internal_globals + SRCS + "internal/globals.cc" + HDRS + "internal/globals.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_severity + absl::raw_logging_internal + absl::strings + absl::time +) + +absl_cc_library( + NAME + log_internal_message + SRCS + "internal/log_message.cc" + HDRS + "internal/log_message.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::base + absl::config + absl::core_headers + absl::errno_saver + absl::inlined_vector + absl::examine_stack + absl::log_internal_config + absl::log_internal_format + absl::log_internal_globals + absl::log_internal_log_sink_set + absl::log_internal_nullguard + absl::log_globals + absl::log_entry + absl::log_severity + absl::log_sink + absl::log_sink_registry + absl::memory + absl::raw_logging_internal + absl::strings + absl::strerror + absl::str_format + absl::time + absl::span +) + +absl_cc_library( + NAME + log_internal_log_sink_set + SRCS + "internal/log_sink_set.cc" + HDRS + "internal/log_sink_set.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::base + absl::cleanup + absl::config + absl::core_headers + absl::log_internal_config + absl::log_internal_globals + absl::log_globals + absl::log_entry + absl::log_severity + absl::log_sink + absl::raw_logging_internal + absl::synchronization + absl::span + absl::strings +) + +absl_cc_library( + NAME + log_internal_nullguard + SRCS + HDRS + "internal/nullguard.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config +) + +absl_cc_library( + NAME + log_internal_nullstream + SRCS + HDRS + "internal/nullstream.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_severity + absl::strings +) + +absl_cc_library( + NAME + log_internal_strip + SRCS + HDRS + "internal/strip.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log_internal_message + absl::log_internal_nullstream + absl::log_severity +) + +absl_cc_library( + NAME + log_internal_test_actions + SRCS + "internal/test_actions.cc" + HDRS + "internal/test_actions.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_entry + absl::log_internal_config + absl::log_severity + absl::strings + absl::time + TESTONLY +) + +absl_cc_library( + NAME + log_internal_test_helpers + SRCS + "internal/test_helpers.cc" + HDRS + "internal/test_helpers.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_globals + absl::log_initialize + absl::log_internal_globals + absl::log_severity + GTest::gtest + TESTONLY +) + +absl_cc_library( + NAME + log_internal_test_matchers + SRCS + "internal/test_matchers.cc" + HDRS + "internal/test_matchers.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_entry + absl::log_internal_config + absl::log_internal_test_helpers + absl::log_severity + absl::strings + absl::time + GTest::gtest + GTest::gmock + TESTONLY +) + +absl_cc_library( + NAME + log_internal_voidify + SRCS + HDRS + "internal/voidify.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config +) + +# Public targets +absl_cc_library( + NAME + check + SRCS + HDRS + "check.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log_internal_check_op + absl::log_internal_conditions + absl::log_internal_message + absl::log_internal_strip + PUBLIC +) + +absl_cc_library( + NAME + die_if_null + SRCS + "die_if_null.cc" + HDRS + "die_if_null.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log + absl::strings + PUBLIC +) + +absl_cc_library( + NAME + log_flags + SRCS + "flags.cc" + HDRS + "flags.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_globals + absl::log_severity + absl::log_internal_config + absl::log_internal_flags + absl::flags + absl::flags_marshalling + absl::strings + PUBLIC +) + +absl_cc_library( + NAME + log_globals + SRCS + "globals.cc" + HDRS + "globals.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::atomic_hook + absl::config + absl::core_headers + absl::hash + absl::log_severity + absl::strings +) + +absl_cc_library( + NAME + log_initialize + SRCS + "initialize.cc" + HDRS + "initialize.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_globals + absl::log_internal_globals + absl::time + PUBLIC +) + +absl_cc_library( + NAME + log + SRCS + HDRS + "log.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log_internal_conditions + absl::log_internal_message + absl::log_internal_strip + PUBLIC +) + +absl_cc_library( + NAME + log_entry + SRCS + "log_entry.cc" + HDRS + "log_entry.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_internal_config + absl::log_severity + absl::span + absl::strings + absl::time + PUBLIC +) + +absl_cc_library( + NAME + log_sink + SRCS + "log_sink.cc" + HDRS + "log_sink.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_entry + PUBLIC +) + +absl_cc_library( + NAME + log_sink_registry + SRCS + HDRS + "log_sink_registry.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_sink + absl::log_internal_log_sink_set + PUBLIC +) + +absl_cc_library( + NAME + log_streamer + SRCS + HDRS + "log_streamer.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log + absl::log_severity + absl::memory + absl::strings + absl::strings_internal + PUBLIC +) + +absl_cc_library( + NAME + scoped_mock_log + SRCS + "scoped_mock_log.cc" + HDRS + "scoped_mock_log.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_entry + absl::log_severity + absl::log_sink + absl::log_sink_registry + absl::raw_logging_internal + absl::strings + GTest::gmock + GTest::gtest + PUBLIC + TESTONLY +) + +# Test targets +absl_cc_test( + NAME + basic_log_test + SRCS + "basic_log_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::base + absl::log + absl::log_entry + absl::log_globals + absl::log_severity + absl::log_internal_test_actions + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::scoped_mock_log + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + check_test + SRCS + "check_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::check + absl::config + absl::core_headers + absl::log_internal_test_helpers + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + die_if_null_test + SRCS + "die_if_null_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::die_if_null + absl::log_internal_test_helpers + GTest::gtest_main +) + +absl_cc_test( + NAME + log_flags_test + SRCS + "flags_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_flags + absl::log_globals + absl::log_internal_flags + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::log_severity + absl::flags + absl::flags_reflection + absl::scoped_mock_log + absl::strings + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_entry_test + SRCS + "log_entry_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_entry + absl::log_internal_format + absl::log_internal_globals + absl::log_internal_test_helpers + absl::log_severity + absl::span + absl::strings + absl::time + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_globals_test + SRCS + "globals_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_globals + absl::log_internal_globals + absl::log_internal_test_helpers + absl::log_severity + absl::scoped_mock_log + GTest::gtest_main +) + +absl_cc_test( + NAME + log_format_test + SRCS + "log_format_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log + absl::log_internal_config + absl::log_internal_test_matchers + absl::scoped_mock_log + absl::strings + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_macro_hygiene_test + SRCS + "log_macro_hygiene_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_severity + absl::scoped_mock_log + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_sink_test + SRCS + "log_sink_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_internal_test_actions + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::log_sink + absl::log_sink_registry + absl::log_severity + absl::raw_logging_internal + absl::scoped_mock_log + absl::strings + GTest::gtest_main +) + +absl_cc_test( + NAME + log_streamer_test + SRCS + "log_streamer_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::base + absl::core_headers + absl::log + absl::log_internal_test_actions + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::log_streamer + absl::log_severity + absl::scoped_mock_log + absl::strings + GTest::gtest_main +) + +absl_cc_test( + NAME + log_modifier_methods_test + SRCS + "log_modifier_methods_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log + absl::log_internal_test_actions + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::log_sink + absl::scoped_mock_log + absl::strings + absl::time + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + scoped_mock_log_test + SRCS + "scoped_mock_log_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_globals + absl::log_internal_globals + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::log_severity + absl::memory + absl::scoped_mock_log + absl::strings + absl::synchronization + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_internal_stderr_log_sink_test + SRCS + "internal/stderr_log_sink_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_globals + absl::log_internal_test_helpers + absl::log_severity + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_stripping_test + SRCS + "stripping_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::check + absl::flags_program_name + absl::log + absl::log_internal_test_helpers + absl::strerror + absl::strings + absl::str_format + GTest::gmock + GTest::gtest_main +) diff --git a/absl/log/basic_log_test.cc b/absl/log/basic_log_test.cc new file mode 100644 index 00000000..bc40f0d0 --- /dev/null +++ b/absl/log/basic_log_test.cc @@ -0,0 +1,440 @@ +// +// Copyright 2022 The Abseil 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. + +// The testcases in this file are expected to pass or be skipped with any value +// of ABSL_MIN_LOG_LEVEL + +#include <cerrno> +#include <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/internal/sysinfo.h" +#include "absl/base/log_severity.h" +#include "absl/log/globals.h" +#include "absl/log/internal/test_actions.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/log/log_entry.h" +#include "absl/log/scoped_mock_log.h" + +namespace { +#if GTEST_HAS_DEATH_TEST +using ::absl::log_internal::DeathTestExpectedLogging; +using ::absl::log_internal::DeathTestUnexpectedLogging; +using ::absl::log_internal::DeathTestValidateExpectations; +using ::absl::log_internal::DiedOfFatal; +using ::absl::log_internal::DiedOfQFatal; +#endif +using ::absl::log_internal::LoggingEnabledAt; +using ::absl::log_internal::LogSeverity; +using ::absl::log_internal::Prefix; +using ::absl::log_internal::SourceBasename; +using ::absl::log_internal::SourceFilename; +using ::absl::log_internal::SourceLine; +using ::absl::log_internal::Stacktrace; +using ::absl::log_internal::TextMessage; +using ::absl::log_internal::ThreadID; +using ::absl::log_internal::TimestampInMatchWindow; +using ::absl::log_internal::Verbosity; +using ::testing::AnyNumber; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::IsTrue; + +class BasicLogTest : public testing::TestWithParam<absl::LogSeverityAtLeast> {}; + +std::string ThresholdName( + testing::TestParamInfo<absl::LogSeverityAtLeast> severity) { + std::stringstream ostr; + ostr << severity.param; + return ostr.str().substr( + severity.param == absl::LogSeverityAtLeast::kInfinity ? 0 : 2); +} + +INSTANTIATE_TEST_SUITE_P(WithParam, BasicLogTest, + testing::Values(absl::LogSeverityAtLeast::kInfo, + absl::LogSeverityAtLeast::kWarning, + absl::LogSeverityAtLeast::kError, + absl::LogSeverityAtLeast::kFatal, + absl::LogSeverityAtLeast::kInfinity), + ThresholdName); + +TEST_P(BasicLogTest, Info) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int log_line = __LINE__ + 1; + auto do_log = [] { LOG(INFO) << "hello world"; }; + + if (LoggingEnabledAt(absl::LogSeverity::kInfo)) { + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("basic_log_test.cc")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(absl::LogSeverity::kInfo)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("hello world")), + Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + literal: "hello world" + })pb")), + Stacktrace(IsEmpty())))); + } + + test_sink.StartCapturingLogs(); + do_log(); +} + +TEST_P(BasicLogTest, Warning) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int log_line = __LINE__ + 1; + auto do_log = [] { LOG(WARNING) << "hello world"; }; + + if (LoggingEnabledAt(absl::LogSeverity::kWarning)) { + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("basic_log_test.cc")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(absl::LogSeverity::kWarning)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("hello world")), + Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + literal: "hello world" + })pb")), + Stacktrace(IsEmpty())))); + } + + test_sink.StartCapturingLogs(); + do_log(); +} + +TEST_P(BasicLogTest, Error) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int log_line = __LINE__ + 1; + auto do_log = [] { LOG(ERROR) << "hello world"; }; + + if (LoggingEnabledAt(absl::LogSeverity::kError)) { + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("basic_log_test.cc")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(absl::LogSeverity::kError)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("hello world")), + Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + literal: "hello world" + })pb")), + Stacktrace(IsEmpty())))); + } + + test_sink.StartCapturingLogs(); + do_log(); +} + +#if GTEST_HAS_DEATH_TEST +using BasicLogDeathTest = BasicLogTest; + +INSTANTIATE_TEST_SUITE_P(WithParam, BasicLogDeathTest, + testing::Values(absl::LogSeverityAtLeast::kInfo, + absl::LogSeverityAtLeast::kFatal, + absl::LogSeverityAtLeast::kInfinity), + ThresholdName); + +TEST_P(BasicLogDeathTest, Fatal) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + const int log_line = __LINE__ + 1; + auto do_log = [] { LOG(FATAL) << "hello world"; }; + + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink( + absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + ::testing::InSequence s; + + // Note the logic in DeathTestValidateExpectations() caters for the case + // of logging being disabled at FATAL level. + + if (LoggingEnabledAt(absl::LogSeverity::kFatal)) { + // The first call without the stack trace. + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("basic_log_test.cc")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(absl::LogSeverity::kFatal)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("hello world")), + Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { literal: "hello world" })pb")), + Stacktrace(IsEmpty())))) + .WillOnce(DeathTestExpectedLogging()); + + // The second call with the stack trace. + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("basic_log_test.cc")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(absl::LogSeverity::kFatal)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("hello world")), + Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { literal: "hello world" })pb")), + Stacktrace(Not(IsEmpty()))))) + .WillOnce(DeathTestExpectedLogging()); + } + + test_sink.StartCapturingLogs(); + do_log(); + }, + DiedOfFatal, DeathTestValidateExpectations()); +} + +TEST_P(BasicLogDeathTest, QFatal) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + const int log_line = __LINE__ + 1; + auto do_log = [] { LOG(QFATAL) << "hello world"; }; + + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink( + absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + if (LoggingEnabledAt(absl::LogSeverity::kFatal)) { + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("basic_log_test.cc")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(absl::LogSeverity::kFatal)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("hello world")), + Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { literal: "hello world" })pb")), + Stacktrace(IsEmpty())))) + .WillOnce(DeathTestExpectedLogging()); + } + + test_sink.StartCapturingLogs(); + do_log(); + }, + DiedOfQFatal, DeathTestValidateExpectations()); +} +#endif + +TEST_P(BasicLogTest, Level) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + for (auto severity : {absl::LogSeverity::kInfo, absl::LogSeverity::kWarning, + absl::LogSeverity::kError}) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int log_line = __LINE__ + 1; + auto do_log = [severity] { LOG(LEVEL(severity)) << "hello world"; }; + + if (LoggingEnabledAt(severity)) { + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("basic_log_test.cc")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(severity)), TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("hello world")), + Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + literal: "hello world" + })pb")), + Stacktrace(IsEmpty())))); + } + test_sink.StartCapturingLogs(); + do_log(); + } +} + +#if GTEST_HAS_DEATH_TEST +TEST_P(BasicLogDeathTest, Level) { + // TODO(b/242568884): re-enable once bug is fixed. + // absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + // Ensure that `severity` is not a compile-time constant to prove that + // `LOG(LEVEL(severity))` works regardless: + auto volatile severity = absl::LogSeverity::kFatal; + + const int log_line = __LINE__ + 1; + auto do_log = [severity] { LOG(LEVEL(severity)) << "hello world"; }; + + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink( + absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + ::testing::InSequence s; + + if (LoggingEnabledAt(absl::LogSeverity::kFatal)) { + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("basic_log_test.cc")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(absl::LogSeverity::kFatal)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("hello world")), + Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { literal: "hello world" })pb")), + Stacktrace(IsEmpty())))) + .WillOnce(DeathTestExpectedLogging()); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("basic_log_test.cc")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(absl::LogSeverity::kFatal)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("hello world")), + Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { literal: "hello world" })pb")), + Stacktrace(Not(IsEmpty()))))) + .WillOnce(DeathTestExpectedLogging()); + } + + test_sink.StartCapturingLogs(); + do_log(); + }, + DiedOfFatal, DeathTestValidateExpectations()); +} +#endif + +TEST_P(BasicLogTest, LevelClampsNegativeValues) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + if (!LoggingEnabledAt(absl::LogSeverity::kInfo)) { + GTEST_SKIP() << "This test cases required INFO log to be enabled"; + return; + } + + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(LogSeverity(Eq(absl::LogSeverity::kInfo)))); + + test_sink.StartCapturingLogs(); + LOG(LEVEL(-1)) << "hello world"; +} + +TEST_P(BasicLogTest, LevelClampsLargeValues) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + if (!LoggingEnabledAt(absl::LogSeverity::kError)) { + GTEST_SKIP() << "This test cases required ERROR log to be enabled"; + return; + } + + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(LogSeverity(Eq(absl::LogSeverity::kError)))); + + test_sink.StartCapturingLogs(); + LOG(LEVEL(static_cast<int>(absl::LogSeverity::kFatal) + 1)) << "hello world"; +} + +TEST(ErrnoPreservationTest, InSeverityExpression) { + errno = 77; + int saved_errno; + LOG(LEVEL((saved_errno = errno, absl::LogSeverity::kInfo))); + EXPECT_THAT(saved_errno, Eq(77)); +} + +TEST(ErrnoPreservationTest, InStreamedExpression) { + if (!LoggingEnabledAt(absl::LogSeverity::kInfo)) { + GTEST_SKIP() << "This test cases required INFO log to be enabled"; + return; + } + + errno = 77; + int saved_errno = 0; + LOG(INFO) << (saved_errno = errno, "hello world"); + EXPECT_THAT(saved_errno, Eq(77)); +} + +TEST(ErrnoPreservationTest, AfterStatement) { + errno = 77; + LOG(INFO); + const int saved_errno = errno; + EXPECT_THAT(saved_errno, Eq(77)); +} + +// Tests that using a variable/parameter in a logging statement suppresses +// unused-variable/parameter warnings. +// ----------------------------------------------------------------------- +class UnusedVariableWarningCompileTest { + // These four don't prove anything unless `ABSL_MIN_LOG_LEVEL` is greater than + // `kInfo`. + static void LoggedVariable() { + const int x = 0; + LOG(INFO) << x; + } + static void LoggedParameter(const int x) { LOG(INFO) << x; } + static void SeverityVariable() { + const int x = 0; + LOG(LEVEL(x)) << "hello world"; + } + static void SeverityParameter(const int x) { LOG(LEVEL(x)) << "hello world"; } +}; + +} // namespace diff --git a/absl/log/check.h b/absl/log/check.h new file mode 100644 index 00000000..30bf76a0 --- /dev/null +++ b/absl/log/check.h @@ -0,0 +1,227 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/check.h +// ----------------------------------------------------------------------------- +// +// This header declares a family of `CHECK` macros. +// +// `CHECK` macros terminate the program with a fatal error if the specified +// condition is not true. +// +// Except for those whose names begin with `DCHECK`, these macros are not +// controlled by `NDEBUG` (cf. `assert`), so the check will be executed +// regardless of compilation mode. `CHECK` and friends are thus useful for +// confirming invariants in situations where continuing to run would be worse +// than terminating, e.g., due to risk of data corruption or security +// compromise. It is also more robust and portable to deliberately terminate +// at a particular place with a useful message and backtrace than to assume some +// ultimately unspecified and unreliable crashing behavior (such as a +// "segmentation fault"). + +#ifndef ABSL_LOG_CHECK_H_ +#define ABSL_LOG_CHECK_H_ + +#include "absl/base/optimization.h" +#include "absl/log/internal/check_op.h" // IWYU pragma: export +#include "absl/log/internal/conditions.h" // IWYU pragma: export +#include "absl/log/internal/log_message.h" // IWYU pragma: export +#include "absl/log/internal/strip.h" // IWYU pragma: export + +// CHECK() +// +// `CHECK` terminates the program with a fatal error if `condition` is not true. +// +// The message may include additional information such as stack traces, when +// available. +// +// Example: +// +// CHECK(!cheese.empty()) << "Out of Cheese"; +// +// Might produce a message like: +// +// Check failed: !cheese.empty() Out of Cheese +#define CHECK(condition) \ + ABSL_LOG_INTERNAL_CONDITION_FATAL(STATELESS, \ + ABSL_PREDICT_FALSE(!(condition))) \ + ABSL_LOG_INTERNAL_CHECK(#condition).InternalStream() + +// QCHECK() +// +// `QCHECK` behaves like `CHECK` but does not print a full stack trace and does +// not run registered error handlers (as `QFATAL`). It is useful when the +// problem is definitely unrelated to program flow, e.g. when validating user +// input. +#define QCHECK(condition) \ + ABSL_LOG_INTERNAL_CONDITION_QFATAL(STATELESS, \ + ABSL_PREDICT_FALSE(!(condition))) \ + ABSL_LOG_INTERNAL_QCHECK(#condition).InternalStream() + +// PCHECK() +// +// `PCHECK` behaves like `CHECK` but appends a description of the current state +// of `errno` to the failure message. +// +// Example: +// +// int fd = open("/var/empty/missing", O_RDONLY); +// PCHECK(fd != -1) << "posix is difficult"; +// +// Might produce a message like: +// +// Check failed: fd != -1 posix is difficult: No such file or directory [2] +#define PCHECK(condition) CHECK(condition).WithPerror() + +// DCHECK() +// +// `DCHECK` behaves like `CHECK` in debug mode and does nothing otherwise (as +// `DLOG`). Unlike with `CHECK` (but as with `assert`), it is not safe to rely +// on evaluation of `condition`: when `NDEBUG` is enabled, DCHECK does not +// evaluate the condition. +#ifndef NDEBUG +#define DCHECK(condition) CHECK(condition) +#else +#define DCHECK(condition) CHECK(true || (condition)) +#endif + +// `CHECK_EQ` and friends are syntactic sugar for `CHECK(x == y)` that +// automatically output the expression being tested and the evaluated values on +// either side. +// +// Example: +// +// int x = 3, y = 5; +// CHECK_EQ(2 * x, y) << "oops!"; +// +// Might produce a message like: +// +// Check failed: 2 * x == y (6 vs. 5) oops! +// +// The values must implement the appropriate comparison operator as well as +// `operator<<(std::ostream&, ...)`. Care is taken to ensure that each +// argument is evaluated exactly once, and that anything which is legal to pass +// as a function argument is legal here. In particular, the arguments may be +// temporary expressions which will end up being destroyed at the end of the +// statement, +// +// Example: +// +// CHECK_EQ(std::string("abc")[1], 'b'); +// +// WARNING: Passing `NULL` as an argument to `CHECK_EQ` and similar macros does +// not compile. Use `nullptr` instead. +#define CHECK_EQ(val1, val2) \ + ABSL_LOG_INTERNAL_CHECK_OP(Check_EQ, ==, val1, val2) +#define CHECK_NE(val1, val2) \ + ABSL_LOG_INTERNAL_CHECK_OP(Check_NE, !=, val1, val2) +#define CHECK_LE(val1, val2) \ + ABSL_LOG_INTERNAL_CHECK_OP(Check_LE, <=, val1, val2) +#define CHECK_LT(val1, val2) ABSL_LOG_INTERNAL_CHECK_OP(Check_LT, <, val1, val2) +#define CHECK_GE(val1, val2) \ + ABSL_LOG_INTERNAL_CHECK_OP(Check_GE, >=, val1, val2) +#define CHECK_GT(val1, val2) ABSL_LOG_INTERNAL_CHECK_OP(Check_GT, >, val1, val2) +#define QCHECK_EQ(val1, val2) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_EQ, ==, val1, val2) +#define QCHECK_NE(val1, val2) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_NE, !=, val1, val2) +#define QCHECK_LE(val1, val2) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_LE, <=, val1, val2) +#define QCHECK_LT(val1, val2) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_LT, <, val1, val2) +#define QCHECK_GE(val1, val2) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_GE, >=, val1, val2) +#define QCHECK_GT(val1, val2) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_GT, >, val1, val2) +#ifndef NDEBUG +#define DCHECK_EQ(val1, val2) CHECK_EQ(val1, val2) +#define DCHECK_NE(val1, val2) CHECK_NE(val1, val2) +#define DCHECK_LE(val1, val2) CHECK_LE(val1, val2) +#define DCHECK_LT(val1, val2) CHECK_LT(val1, val2) +#define DCHECK_GE(val1, val2) CHECK_GE(val1, val2) +#define DCHECK_GT(val1, val2) CHECK_GT(val1, val2) +#else // ndef NDEBUG +#define DCHECK_EQ(val1, val2) ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#define DCHECK_NE(val1, val2) ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#define DCHECK_LE(val1, val2) ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#define DCHECK_LT(val1, val2) ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#define DCHECK_GE(val1, val2) ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#define DCHECK_GT(val1, val2) ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#endif // def NDEBUG + +// `CHECK_OK` and friends validate that the provided `absl::Status` or +// `absl::StatusOr<T>` is OK. If it isn't, they print a failure message that +// includes the actual status. +// +// As with all `DCHECK` variants, `DCHECK_OK` has no effect (not even +// evaluating its argument) if `NDEBUG` is enabled. +// +// Example: +// +// CHECK_OK(FunctionReturnsStatus(x, y, z)) << "oops!"; +// +// Might produce a message like: +// +// Check failed: FunctionReturnsStatus(x, y, z) is OK (ABORTED: timeout) oops! +#define CHECK_OK(status) ABSL_LOG_INTERNAL_CHECK_OK(status) +#define QCHECK_OK(status) ABSL_LOG_INTERNAL_QCHECK_OK(status) +#ifndef NDEBUG +#define DCHECK_OK(status) ABSL_LOG_INTERNAL_CHECK_OK(status) +#else +#define DCHECK_OK(status) ABSL_LOG_INTERNAL_DCHECK_NOP(status, nullptr) +#endif + +// `CHECK_STREQ` and friends provide `CHECK_EQ` functionality for C strings, +// i.e., nul-terminated char arrays. The `CASE` versions are case-insensitive. +// +// Example: +// +// CHECK_STREQ(argv[0], "./skynet"); +// +// Note that both arguments may be temporary strings which are destroyed by the +// compiler at the end of the current full expression. +// +// Example: +// +// CHECK_STREQ(Foo().c_str(), Bar().c_str()); +#define CHECK_STREQ(s1, s2) \ + ABSL_LOG_INTERNAL_CHECK_STROP(strcmp, ==, true, s1, s2) +#define CHECK_STRNE(s1, s2) \ + ABSL_LOG_INTERNAL_CHECK_STROP(strcmp, !=, false, s1, s2) +#define CHECK_STRCASEEQ(s1, s2) \ + ABSL_LOG_INTERNAL_CHECK_STROP(strcasecmp, ==, true, s1, s2) +#define CHECK_STRCASENE(s1, s2) \ + ABSL_LOG_INTERNAL_CHECK_STROP(strcasecmp, !=, false, s1, s2) +#define QCHECK_STREQ(s1, s2) \ + ABSL_LOG_INTERNAL_QCHECK_STROP(strcmp, ==, true, s1, s2) +#define QCHECK_STRNE(s1, s2) \ + ABSL_LOG_INTERNAL_QCHECK_STROP(strcmp, !=, false, s1, s2) +#define QCHECK_STRCASEEQ(s1, s2) \ + ABSL_LOG_INTERNAL_QCHECK_STROP(strcasecmp, ==, true, s1, s2) +#define QCHECK_STRCASENE(s1, s2) \ + ABSL_LOG_INTERNAL_QCHECK_STROP(strcasecmp, !=, false, s1, s2) +#ifndef NDEBUG +#define DCHECK_STREQ(s1, s2) CHECK_STREQ(s1, s2) +#define DCHECK_STRCASEEQ(s1, s2) CHECK_STRCASEEQ(s1, s2) +#define DCHECK_STRNE(s1, s2) CHECK_STRNE(s1, s2) +#define DCHECK_STRCASENE(s1, s2) CHECK_STRCASENE(s1, s2) +#else // ndef NDEBUG +#define DCHECK_STREQ(s1, s2) ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) +#define DCHECK_STRCASEEQ(s1, s2) ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) +#define DCHECK_STRNE(s1, s2) ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) +#define DCHECK_STRCASENE(s1, s2) ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) +#endif // def NDEBUG + +#endif // ABSL_LOG_CHECK_H_ diff --git a/absl/log/check_test.cc b/absl/log/check_test.cc new file mode 100644 index 00000000..4ce9d872 --- /dev/null +++ b/absl/log/check_test.cc @@ -0,0 +1,433 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/check.h" + +#include <ostream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/log/internal/test_helpers.h" + +namespace { +using ::testing::AllOf; +using ::testing::HasSubstr; +using ::testing::Not; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +#if GTEST_HAS_DEATH_TEST + +TEST(CHECKDeathTest, TestBasicValues) { + CHECK(true); + + EXPECT_DEATH(CHECK(false), "Check failed: false"); + + int i = 2; + CHECK(i != 3); // NOLINT +} + +#endif // GTEST_HAS_DEATH_TEST + +TEST(CHECKTest, TestLogicExpressions) { + int i = 5; + CHECK(i > 0 && i < 10); + CHECK(i < 0 || i > 3); +} + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L +ABSL_CONST_INIT const auto global_var_check = [](int i) { + CHECK(i > 0); // NOLINT + return i + 1; +}(3); + +ABSL_CONST_INIT const auto global_var = [](int i) { + CHECK_GE(i, 0); // NOLINT + return i + 1; +}(global_var_check); +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG + +TEST(CHECKTest, TestPlacementsInCompoundStatements) { + // check placement inside if/else clauses + if (true) CHECK(true); + + if (false) + ; // NOLINT + else + CHECK(true); + + switch (0) + case 0: + CHECK(true); // NOLINT + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L + constexpr auto var = [](int i) { + CHECK(i > 0); // NOLINT + return i + 1; + }(global_var); + (void)var; +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG +} + +TEST(CHECKTest, TestBoolConvertible) { + struct Tester { + } tester; + CHECK([&]() { return &tester; }()); +} + +#if GTEST_HAS_DEATH_TEST + +TEST(CHECKDeathTest, TestChecksWithSideeffects) { + int var = 0; + CHECK([&var]() { + ++var; + return true; + }()); + EXPECT_EQ(var, 1); + + EXPECT_DEATH(CHECK([&var]() { + ++var; + return false; + }()) << var, + "Check failed: .* 2"); +} + +#endif // GTEST_HAS_DEATH_TEST + +#if GTEST_HAS_DEATH_TEST + +TEST(CHECKDeachTest, TestOrderOfInvocationsBetweenCheckAndMessage) { + int counter = 0; + + auto GetStr = [&counter]() -> std::string { + return counter++ == 0 ? "" : "non-empty"; + }; + + EXPECT_DEATH(CHECK(!GetStr().empty()) << GetStr(), HasSubstr("non-empty")); +} + +TEST(CHECKTest, TestSecondaryFailure) { + auto FailingRoutine = []() { + CHECK(false) << "Secondary"; + return false; + }; + EXPECT_DEATH(CHECK(FailingRoutine()) << "Primary", + AllOf(HasSubstr("Secondary"), Not(HasSubstr("Primary")))); +} + +TEST(CHECKTest, TestSecondaryFailureInMessage) { + auto MessageGen = []() { + CHECK(false) << "Secondary"; + return "Primary"; + }; + EXPECT_DEATH(CHECK(false) << MessageGen(), + AllOf(HasSubstr("Secondary"), Not(HasSubstr("Primary")))); +} + +#endif // GTEST_HAS_DEATH_TEST + +TEST(CHECKTest, TestBinaryChecksWithPrimitives) { + CHECK_EQ(1, 1); + CHECK_NE(1, 2); + CHECK_GE(1, 1); + CHECK_GE(2, 1); + CHECK_LE(1, 1); + CHECK_LE(1, 2); + CHECK_GT(2, 1); + CHECK_LT(1, 2); +} + +// For testing using CHECK*() on anonymous enums. +enum { CASE_A, CASE_B }; + +TEST(CHECKTest, TestBinaryChecksWithEnumValues) { + // Tests using CHECK*() on anonymous enums. + CHECK_EQ(CASE_A, CASE_A); + CHECK_NE(CASE_A, CASE_B); + CHECK_GE(CASE_A, CASE_A); + CHECK_GE(CASE_B, CASE_A); + CHECK_LE(CASE_A, CASE_A); + CHECK_LE(CASE_A, CASE_B); + CHECK_GT(CASE_B, CASE_A); + CHECK_LT(CASE_A, CASE_B); +} + +TEST(CHECKTest, TestBinaryChecksWithNullptr) { + const void* p_null = nullptr; + const void* p_not_null = &p_null; + CHECK_EQ(p_null, nullptr); + CHECK_EQ(nullptr, p_null); + CHECK_NE(p_not_null, nullptr); + CHECK_NE(nullptr, p_not_null); +} + +#if GTEST_HAS_DEATH_TEST + +// Test logging of various char-typed values by failing CHECK*(). +TEST(CHECKDeathTest, TestComparingCharsValues) { + { + char a = ';'; + char b = 'b'; + EXPECT_DEATH(CHECK_EQ(a, b), "Check failed: a == b \\(';' vs. 'b'\\)"); + b = 1; + EXPECT_DEATH(CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. char value 1\\)"); + } + { + signed char a = ';'; + signed char b = 'b'; + EXPECT_DEATH(CHECK_EQ(a, b), "Check failed: a == b \\(';' vs. 'b'\\)"); + b = -128; + EXPECT_DEATH(CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. signed char value -128\\)"); + } + { + unsigned char a = ';'; + unsigned char b = 'b'; + EXPECT_DEATH(CHECK_EQ(a, b), "Check failed: a == b \\(';' vs. 'b'\\)"); + b = 128; + EXPECT_DEATH(CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. unsigned char value 128\\)"); + } +} + +TEST(CHECKDeathTest, TestNullValuesAreReportedCleanly) { + const char* a = nullptr; + const char* b = nullptr; + EXPECT_DEATH(CHECK_NE(a, b), + "Check failed: a != b \\(\\(null\\) vs. \\(null\\)\\)"); + + a = "xx"; + EXPECT_DEATH(CHECK_EQ(a, b), "Check failed: a == b \\(xx vs. \\(null\\)\\)"); + EXPECT_DEATH(CHECK_EQ(b, a), "Check failed: b == a \\(\\(null\\) vs. xx\\)"); + + std::nullptr_t n{}; + EXPECT_DEATH(CHECK_NE(n, nullptr), + "Check failed: n != nullptr \\(\\(null\\) vs. \\(null\\)\\)"); +} + +#endif // GTEST_HAS_DEATH_TEST + +TEST(CHECKTest, TestSTREQ) { + CHECK_STREQ("this", "this"); + CHECK_STREQ(nullptr, nullptr); + CHECK_STRCASEEQ("this", "tHiS"); + CHECK_STRCASEEQ(nullptr, nullptr); + CHECK_STRNE("this", "tHiS"); + CHECK_STRNE("this", nullptr); + CHECK_STRCASENE("this", "that"); + CHECK_STRCASENE(nullptr, "that"); + CHECK_STREQ((std::string("a") + "b").c_str(), "ab"); + CHECK_STREQ(std::string("test").c_str(), + (std::string("te") + std::string("st")).c_str()); +} + +TEST(CHECKTest, TestComparisonPlacementsInCompoundStatements) { + // check placement inside if/else clauses + if (true) CHECK_EQ(1, 1); + if (true) CHECK_STREQ("c", "c"); + + if (false) + ; // NOLINT + else + CHECK_LE(0, 1); + + if (false) + ; // NOLINT + else + CHECK_STRNE("a", "b"); + + switch (0) + case 0: + CHECK_NE(1, 0); + + switch (0) + case 0: + CHECK_STRCASEEQ("A", "a"); + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L + constexpr auto var = [](int i) { + CHECK_GT(i, 0); + return i + 1; + }(global_var); + (void)var; + + // CHECK_STR... checks are not supported in constexpr routines. + // constexpr auto var2 = [](int i) { + // CHECK_STRNE("c", "d"); + // return i + 1; + // }(global_var); + +#if defined(__GNUC__) + int var3 = (({ CHECK_LE(1, 2); }), global_var < 10) ? 1 : 0; + (void)var3; + + int var4 = (({ CHECK_STREQ("a", "a"); }), global_var < 10) ? 1 : 0; + (void)var4; +#endif // __GNUC__ +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG +} + +TEST(CHECKTest, TestDCHECK) { +#ifdef NDEBUG + DCHECK(1 == 2) << " DCHECK's shouldn't be compiled in normal mode"; +#endif + DCHECK(1 == 1); // NOLINT(readability/check) + DCHECK_EQ(1, 1); + DCHECK_NE(1, 2); + DCHECK_GE(1, 1); + DCHECK_GE(2, 1); + DCHECK_LE(1, 1); + DCHECK_LE(1, 2); + DCHECK_GT(2, 1); + DCHECK_LT(1, 2); + + // Test DCHECK on std::nullptr_t + const void* p_null = nullptr; + const void* p_not_null = &p_null; + DCHECK_EQ(p_null, nullptr); + DCHECK_EQ(nullptr, p_null); + DCHECK_NE(p_not_null, nullptr); + DCHECK_NE(nullptr, p_not_null); +} + +TEST(CHECKTest, TestQCHECK) { + // The tests that QCHECK does the same as CHECK + QCHECK(1 == 1); // NOLINT(readability/check) + QCHECK_EQ(1, 1); + QCHECK_NE(1, 2); + QCHECK_GE(1, 1); + QCHECK_GE(2, 1); + QCHECK_LE(1, 1); + QCHECK_LE(1, 2); + QCHECK_GT(2, 1); + QCHECK_LT(1, 2); + + // Tests using QCHECK*() on anonymous enums. + QCHECK_EQ(CASE_A, CASE_A); + QCHECK_NE(CASE_A, CASE_B); + QCHECK_GE(CASE_A, CASE_A); + QCHECK_GE(CASE_B, CASE_A); + QCHECK_LE(CASE_A, CASE_A); + QCHECK_LE(CASE_A, CASE_B); + QCHECK_GT(CASE_B, CASE_A); + QCHECK_LT(CASE_A, CASE_B); +} + +TEST(CHECKTest, TestQCHECKPlacementsInCompoundStatements) { + // check placement inside if/else clauses + if (true) QCHECK(true); + + if (false) + ; // NOLINT + else + QCHECK(true); + + if (false) + ; // NOLINT + else + QCHECK(true); + + switch (0) + case 0: + QCHECK(true); + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L + constexpr auto var = [](int i) { + QCHECK(i > 0); // NOLINT + return i + 1; + }(global_var); + (void)var; + +#if defined(__GNUC__) + int var2 = (({ CHECK_LE(1, 2); }), global_var < 10) ? 1 : 0; + (void)var2; +#endif // __GNUC__ +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG +} + +class ComparableType { + public: + explicit ComparableType(int v) : v_(v) {} + + void MethodWithCheck(int i) { + CHECK_EQ(*this, i); + CHECK_EQ(i, *this); + } + + int Get() const { return v_; } + + private: + friend bool operator==(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ == rhs.v_; + } + friend bool operator!=(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ != rhs.v_; + } + friend bool operator<(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ < rhs.v_; + } + friend bool operator<=(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ <= rhs.v_; + } + friend bool operator>(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ > rhs.v_; + } + friend bool operator>=(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ >= rhs.v_; + } + friend bool operator==(const ComparableType& lhs, int rhs) { + return lhs.v_ == rhs; + } + friend bool operator==(int lhs, const ComparableType& rhs) { + return lhs == rhs.v_; + } + + friend std::ostream& operator<<(std::ostream& out, const ComparableType& v) { + return out << "ComparableType{" << v.Get() << "}"; + } + + int v_; +}; + +TEST(CHECKTest, TestUserDefinedCompOp) { + CHECK_EQ(ComparableType{0}, ComparableType{0}); + CHECK_NE(ComparableType{1}, ComparableType{2}); + CHECK_LT(ComparableType{1}, ComparableType{2}); + CHECK_LE(ComparableType{1}, ComparableType{2}); + CHECK_GT(ComparableType{2}, ComparableType{1}); + CHECK_GE(ComparableType{2}, ComparableType{2}); +} + +TEST(CHECKTest, TestCheckInMethod) { + ComparableType v{1}; + v.MethodWithCheck(1); +} + +TEST(CHECKDeathTest, TestUserDefinedStreaming) { + ComparableType v1{1}; + ComparableType v2{2}; + + EXPECT_DEATH( + CHECK_EQ(v1, v2), + HasSubstr( + "Check failed: v1 == v2 (ComparableType{1} vs. ComparableType{2})")); +} + +} // namespace diff --git a/absl/log/die_if_null.cc b/absl/log/die_if_null.cc new file mode 100644 index 00000000..19c6a28e --- /dev/null +++ b/absl/log/die_if_null.cc @@ -0,0 +1,32 @@ +// Copyright 2022 The Abseil 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. + +#include "absl/log/die_if_null.h" + +#include "absl/base/config.h" +#include "absl/log/log.h" +#include "absl/strings/str_cat.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +void DieBecauseNull(const char* file, int line, const char* exprtext) { + LOG(FATAL).AtLocation(file, line) + << absl::StrCat("Check failed: '", exprtext, "' Must be non-null"); +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/die_if_null.h b/absl/log/die_if_null.h new file mode 100644 index 00000000..127a9ac8 --- /dev/null +++ b/absl/log/die_if_null.h @@ -0,0 +1,76 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/die_if_null.h +// ----------------------------------------------------------------------------- +// +// This header declares macro `ABSL_DIE_IF_NULL`. + +#ifndef ABSL_LOG_DIE_IF_NULL_H_ +#define ABSL_LOG_DIE_IF_NULL_H_ + +#include <stdint.h> + +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/optimization.h" + +// ABSL_DIE_IF_NULL() +// +// `ABSL_DIE_IF_NULL` behaves as `CHECK_NE` against `nullptr` but *also* +// "returns" its argument. It is useful in initializers where statements (like +// `CHECK_NE`) can't be used. Outside initializers, prefer `CHECK` or +// `CHECK_NE`. `ABSL_DIE_IF_NULL` works for both raw pointers and (compatible) +// smart pointers including `std::unique_ptr` and `std::shared_ptr`; more +// generally, it works for any type that can be compared to nullptr_t. For +// types that aren't raw pointers, `ABSL_DIE_IF_NULL` returns a reference to +// its argument, preserving the value category. Example: +// +// Foo() : bar_(ABSL_DIE_IF_NULL(MethodReturningUniquePtr())) {} +// +// Use `CHECK(ptr)` or `CHECK(ptr != nullptr)` if the returned pointer is +// unused. +#define ABSL_DIE_IF_NULL(val) \ + ::absl::log_internal::DieIfNull(__FILE__, __LINE__, #val, (val)) + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// Crashes the process after logging `exprtext` annotated at the `file` and +// `line` location. Called when `ABSL_DIE_IF_NULL` fails. Calling this function +// generates less code than its implementation would if inlined, for a slight +// code size reduction each time `ABSL_DIE_IF_NULL` is called. +ABSL_ATTRIBUTE_NORETURN ABSL_ATTRIBUTE_NOINLINE void DieBecauseNull( + const char* file, int line, const char* exprtext); + +// Helper for `ABSL_DIE_IF_NULL`. +template <typename T> +ABSL_MUST_USE_RESULT T DieIfNull(const char* file, int line, + const char* exprtext, T&& t) { + if (ABSL_PREDICT_FALSE(t == nullptr)) { + // Call a non-inline helper function for a small code size improvement. + DieBecauseNull(file, line, exprtext); + } + return std::forward<T>(t); +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_DIE_IF_NULL_H_ diff --git a/absl/log/die_if_null_test.cc b/absl/log/die_if_null_test.cc new file mode 100644 index 00000000..b0aab781 --- /dev/null +++ b/absl/log/die_if_null_test.cc @@ -0,0 +1,107 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/die_if_null.h" + +#include <stdint.h> + +#include <memory> +#include <utility> + +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/log/internal/test_helpers.h" + +namespace { + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +// TODO(b/69907837): Revisit these tests with the goal of making them less +// convoluted. +TEST(AbslDieIfNull, Simple) { + int64_t t; + void* ptr = static_cast<void*>(&t); + void* ref = ABSL_DIE_IF_NULL(ptr); + ASSERT_EQ(ptr, ref); + + char* t_as_char; + t_as_char = ABSL_DIE_IF_NULL(reinterpret_cast<char*>(&t)); + (void)t_as_char; + + unsigned char* t_as_uchar; + t_as_uchar = ABSL_DIE_IF_NULL(reinterpret_cast<unsigned char*>(&t)); + (void)t_as_uchar; + + int* t_as_int; + t_as_int = ABSL_DIE_IF_NULL(reinterpret_cast<int*>(&t)); + (void)t_as_int; + + int64_t* t_as_int64_t; + t_as_int64_t = ABSL_DIE_IF_NULL(reinterpret_cast<int64_t*>(&t)); + (void)t_as_int64_t; + + std::unique_ptr<int64_t> sptr(new int64_t); + EXPECT_EQ(sptr.get(), ABSL_DIE_IF_NULL(sptr).get()); + ABSL_DIE_IF_NULL(sptr).reset(); + + int64_t* int_ptr = new int64_t(); + EXPECT_EQ(int_ptr, ABSL_DIE_IF_NULL(std::unique_ptr<int64_t>(int_ptr)).get()); +} + +#if GTEST_HAS_DEATH_TEST +TEST(DeathCheckAbslDieIfNull, Simple) { + void* ptr; + ASSERT_DEATH({ ptr = ABSL_DIE_IF_NULL(nullptr); }, ""); + (void)ptr; + + std::unique_ptr<int64_t> sptr; + ASSERT_DEATH(ptr = ABSL_DIE_IF_NULL(sptr).get(), ""); +} +#endif + +// Ensures that ABSL_DIE_IF_NULL works with C++11's std::unique_ptr and +// std::shared_ptr. +TEST(AbslDieIfNull, DoesNotCompareSmartPointerToNULL) { + std::unique_ptr<int> up(new int); + EXPECT_EQ(&up, &ABSL_DIE_IF_NULL(up)); + ABSL_DIE_IF_NULL(up).reset(); + + std::shared_ptr<int> sp(new int); + EXPECT_EQ(&sp, &ABSL_DIE_IF_NULL(sp)); + ABSL_DIE_IF_NULL(sp).reset(); +} + +// Verifies that ABSL_DIE_IF_NULL returns an rvalue reference if its argument is +// an rvalue reference. +TEST(AbslDieIfNull, PreservesRValues) { + int64_t* ptr = new int64_t(); + auto uptr = ABSL_DIE_IF_NULL(std::unique_ptr<int64_t>(ptr)); + EXPECT_EQ(ptr, uptr.get()); +} + +// Verifies that ABSL_DIE_IF_NULL returns an lvalue if its argument is an +// lvalue. +TEST(AbslDieIfNull, PreservesLValues) { + int64_t array[2] = {0}; + int64_t* a = array + 0; + int64_t* b = array + 1; + using std::swap; + swap(ABSL_DIE_IF_NULL(a), ABSL_DIE_IF_NULL(b)); + EXPECT_EQ(array + 1, a); + EXPECT_EQ(array + 0, b); +} + +} // namespace diff --git a/absl/log/flags.cc b/absl/log/flags.cc new file mode 100644 index 00000000..b5308881 --- /dev/null +++ b/absl/log/flags.cc @@ -0,0 +1,112 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/internal/flags.h" + +#include <stddef.h> + +#include <algorithm> +#include <cstdlib> +#include <string> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/flags/flag.h" +#include "absl/flags/marshalling.h" +#include "absl/log/globals.h" +#include "absl/log/internal/config.h" +#include "absl/strings/numbers.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +namespace { + +void SyncLoggingFlags() { + absl::SetFlag(&FLAGS_minloglevel, static_cast<int>(absl::MinLogLevel())); + absl::SetFlag(&FLAGS_log_prefix, absl::ShouldPrependLogPrefix()); +} + +bool RegisterSyncLoggingFlags() { + log_internal::SetLoggingGlobalsListener(&SyncLoggingFlags); + return true; +} + +ABSL_ATTRIBUTE_UNUSED const bool unused = RegisterSyncLoggingFlags(); + +template <typename T> +T GetFromEnv(const char* varname, T dflt) { + const char* val = ::getenv(varname); + if (val != nullptr) { + std::string err; + ABSL_INTERNAL_CHECK(absl::ParseFlag(val, &dflt, &err), err.c_str()); + } + return dflt; +} + +constexpr absl::LogSeverityAtLeast StderrThresholdDefault() { + return absl::LogSeverityAtLeast::kError; +} + +} // namespace +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +ABSL_FLAG(int, stderrthreshold, + static_cast<int>(absl::log_internal::StderrThresholdDefault()), + "Log messages at or above this threshold level are copied to stderr.") + .OnUpdate([] { + absl::log_internal::RawSetStderrThreshold( + static_cast<absl::LogSeverityAtLeast>( + absl::GetFlag(FLAGS_stderrthreshold))); + }); + +ABSL_FLAG(int, minloglevel, static_cast<int>(absl::LogSeverityAtLeast::kInfo), + "Messages logged at a lower level than this don't actually " + "get logged anywhere") + .OnUpdate([] { + absl::log_internal::RawSetMinLogLevel( + static_cast<absl::LogSeverityAtLeast>( + absl::GetFlag(FLAGS_minloglevel))); + }); + +ABSL_FLAG(std::string, log_backtrace_at, "", + "Emit a backtrace when logging at file:linenum.") + .OnUpdate([] { + const std::string log_backtrace_at = + absl::GetFlag(FLAGS_log_backtrace_at); + if (log_backtrace_at.empty()) return; + + const size_t last_colon = log_backtrace_at.rfind(':'); + if (last_colon == log_backtrace_at.npos) return; + + const absl::string_view file = + absl::string_view(log_backtrace_at).substr(0, last_colon); + int line; + if (absl::SimpleAtoi( + absl::string_view(log_backtrace_at).substr(last_colon + 1), + &line)) { + absl::SetLogBacktraceLocation(file, line); + } + }); + +ABSL_FLAG(bool, log_prefix, true, + "Prepend the log prefix to the start of each log line") + .OnUpdate([] { + absl::log_internal::RawEnableLogPrefix(absl::GetFlag(FLAGS_log_prefix)); + }); diff --git a/absl/log/flags.h b/absl/log/flags.h new file mode 100644 index 00000000..146cfdd6 --- /dev/null +++ b/absl/log/flags.h @@ -0,0 +1,43 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/flags.h +// ----------------------------------------------------------------------------- +// + +#ifndef ABSL_LOG_FLAGS_H_ +#define ABSL_LOG_FLAGS_H_ + +// The Abseil Logging library supports the following command line flags to +// configure logging behavior at runtime: +// +// --stderrthreshold=<value> +// Log messages at or above this threshold level are copied to stderr. +// +// --minloglevel=<value> +// Messages logged at a lower level than this are discarded and don't actually +// get logged anywhere. +// +// --log_backtrace_at=<file:linenum> +// Emit a backtrace (stack trace) when logging at file:linenum. +// +// To use these commandline flags, the //absl/log:flags library must be +// explicitly linked, and absl::ParseCommandLine() must be called before the +// call to absl::InitializeLog(). +// +// To configure the Log library programmatically, use the interfaces defined in +// absl/log/globals.h. + +#endif // ABSL_LOG_FLAGS_H_ diff --git a/absl/log/flags_test.cc b/absl/log/flags_test.cc new file mode 100644 index 00000000..7a803152 --- /dev/null +++ b/absl/log/flags_test.cc @@ -0,0 +1,181 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/internal/flags.h" + +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/log_severity.h" +#include "absl/flags/flag.h" +#include "absl/flags/reflection.h" +#include "absl/log/globals.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/log/scoped_mock_log.h" +#include "absl/strings/str_cat.h" + +namespace { +using ::absl::log_internal::TextMessage; + +using ::testing::HasSubstr; +using ::testing::Not; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +constexpr static absl::LogSeverityAtLeast DefaultStderrThreshold() { + return absl::LogSeverityAtLeast::kError; +} + +class LogFlagsTest : public ::testing::Test { + protected: + absl::FlagSaver flag_saver_; +}; + +TEST_F(LogFlagsTest, StderrKnobsDefault) { + EXPECT_EQ(absl::StderrThreshold(), DefaultStderrThreshold()); +} + +TEST_F(LogFlagsTest, SetStderrThreshold) { + absl::SetFlag(&FLAGS_stderrthreshold, + static_cast<int>(absl::LogSeverityAtLeast::kInfo)); + + EXPECT_EQ(absl::StderrThreshold(), absl::LogSeverityAtLeast::kInfo); + + absl::SetFlag(&FLAGS_stderrthreshold, + static_cast<int>(absl::LogSeverityAtLeast::kError)); + + EXPECT_EQ(absl::StderrThreshold(), absl::LogSeverityAtLeast::kError); +} + +TEST_F(LogFlagsTest, SetMinLogLevel) { + absl::SetFlag(&FLAGS_minloglevel, + static_cast<int>(absl::LogSeverityAtLeast::kError)); + + EXPECT_EQ(absl::MinLogLevel(), absl::LogSeverityAtLeast::kError); + + absl::log_internal::ScopedMinLogLevel scoped_min_log_level( + absl::LogSeverityAtLeast::kWarning); + + EXPECT_EQ(absl::GetFlag(FLAGS_minloglevel), + static_cast<int>(absl::LogSeverityAtLeast::kWarning)); +} + +TEST_F(LogFlagsTest, PrependLogPrefix) { + absl::SetFlag(&FLAGS_log_prefix, false); + + EXPECT_EQ(absl::ShouldPrependLogPrefix(), false); + + absl::EnableLogPrefix(true); + + EXPECT_EQ(absl::GetFlag(FLAGS_log_prefix), true); +} + +TEST_F(LogFlagsTest, EmptyBacktraceAtFlag) { + absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); + absl::SetFlag(&FLAGS_log_backtrace_at, ""); + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << "hello world"; +} + +TEST_F(LogFlagsTest, BacktraceAtNonsense) { + absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); + absl::SetFlag(&FLAGS_log_backtrace_at, "gibberish"); + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << "hello world"; +} + +TEST_F(LogFlagsTest, BacktraceAtWrongFile) { + absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); + const int log_line = __LINE__ + 1; + auto do_log = [] { LOG(INFO) << "hello world"; }; + absl::SetFlag(&FLAGS_log_backtrace_at, + absl::StrCat("some_other_file.cc:", log_line)); + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); + + test_sink.StartCapturingLogs(); + do_log(); +} + +TEST_F(LogFlagsTest, BacktraceAtWrongLine) { + absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); + const int log_line = __LINE__ + 1; + auto do_log = [] { LOG(INFO) << "hello world"; }; + absl::SetFlag(&FLAGS_log_backtrace_at, + absl::StrCat("flags_test.cc:", log_line + 1)); + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); + + test_sink.StartCapturingLogs(); + do_log(); +} + +TEST_F(LogFlagsTest, BacktraceAtWholeFilename) { + absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); + const int log_line = __LINE__ + 1; + auto do_log = [] { LOG(INFO) << "hello world"; }; + absl::SetFlag(&FLAGS_log_backtrace_at, absl::StrCat(__FILE__, ":", log_line)); + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); + + test_sink.StartCapturingLogs(); + do_log(); +} + +TEST_F(LogFlagsTest, BacktraceAtNonmatchingSuffix) { + absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); + const int log_line = __LINE__ + 1; + auto do_log = [] { LOG(INFO) << "hello world"; }; + absl::SetFlag(&FLAGS_log_backtrace_at, + absl::StrCat("flags_test.cc:", log_line, "gibberish")); + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); + + test_sink.StartCapturingLogs(); + do_log(); +} + +TEST_F(LogFlagsTest, LogsBacktrace) { + absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); + const int log_line = __LINE__ + 1; + auto do_log = [] { LOG(INFO) << "hello world"; }; + absl::SetFlag(&FLAGS_log_backtrace_at, + absl::StrCat("flags_test.cc:", log_line)); + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(TextMessage(HasSubstr("(stacktrace:")))); + + test_sink.StartCapturingLogs(); + do_log(); +} + +} // namespace diff --git a/absl/log/globals.cc b/absl/log/globals.cc new file mode 100644 index 00000000..6dfe81f0 --- /dev/null +++ b/absl/log/globals.cc @@ -0,0 +1,148 @@ +// Copyright 2022 The Abseil 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. + +#include "absl/log/globals.h" + +#include <stddef.h> +#include <stdint.h> + +#include <atomic> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/internal/atomic_hook.h" +#include "absl/base/log_severity.h" +#include "absl/hash/hash.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace { + +// These atomics represent logging library configuration. +// Integer types are used instead of absl::LogSeverity to ensure that a +// lock-free std::atomic is used when possible. +ABSL_CONST_INIT std::atomic<int> min_log_level{ + static_cast<int>(absl::LogSeverityAtLeast::kInfo)}; +ABSL_CONST_INIT std::atomic<int> stderrthreshold{ + static_cast<int>(absl::LogSeverityAtLeast::kError)}; +// We evaluate this value as a hash comparison to avoid having to +// hold a mutex or make a copy (to access the value of a string-typed flag) in +// very hot codepath. +ABSL_CONST_INIT std::atomic<size_t> log_backtrace_at_hash{0}; +ABSL_CONST_INIT std::atomic<bool> prepend_log_prefix{true}; + +ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES +absl::base_internal::AtomicHook<log_internal::LoggingGlobalsListener> + logging_globals_listener; + +size_t HashSiteForLogBacktraceAt(absl::string_view file, int line) { + return absl::HashOf(file, line); +} + +void TriggerLoggingGlobalsListener() { + auto* listener = logging_globals_listener.Load(); + if (listener != nullptr) listener(); +} + +} // namespace + +namespace log_internal { + +void RawSetMinLogLevel(absl::LogSeverityAtLeast severity) { + min_log_level.store(static_cast<int>(severity), std::memory_order_release); +} + +void RawSetStderrThreshold(absl::LogSeverityAtLeast severity) { + stderrthreshold.store(static_cast<int>(severity), std::memory_order_release); +} + +void RawEnableLogPrefix(bool on_off) { + prepend_log_prefix.store(on_off, std::memory_order_release); +} + +void SetLoggingGlobalsListener(LoggingGlobalsListener l) { + logging_globals_listener.Store(l); +} + +} // namespace log_internal + +absl::LogSeverityAtLeast MinLogLevel() { + return static_cast<absl::LogSeverityAtLeast>( + min_log_level.load(std::memory_order_acquire)); +} + +void SetMinLogLevel(absl::LogSeverityAtLeast severity) { + log_internal::RawSetMinLogLevel(severity); + TriggerLoggingGlobalsListener(); +} + +namespace log_internal { + +ScopedMinLogLevel::ScopedMinLogLevel(absl::LogSeverityAtLeast severity) + : saved_severity_(absl::MinLogLevel()) { + absl::SetMinLogLevel(severity); +} +ScopedMinLogLevel::~ScopedMinLogLevel() { + absl::SetMinLogLevel(saved_severity_); +} + +} // namespace log_internal + +absl::LogSeverityAtLeast StderrThreshold() { + return static_cast<absl::LogSeverityAtLeast>( + stderrthreshold.load(std::memory_order_acquire)); +} + +void SetStderrThreshold(absl::LogSeverityAtLeast severity) { + log_internal::RawSetStderrThreshold(severity); + TriggerLoggingGlobalsListener(); +} + +ScopedStderrThreshold::ScopedStderrThreshold(absl::LogSeverityAtLeast severity) + : saved_severity_(absl::StderrThreshold()) { + absl::SetStderrThreshold(severity); +} + +ScopedStderrThreshold::~ScopedStderrThreshold() { + absl::SetStderrThreshold(saved_severity_); +} + +namespace log_internal { + +bool ShouldLogBacktraceAt(absl::string_view file, int line) { + const size_t flag_hash = + log_backtrace_at_hash.load(std::memory_order_acquire); + + return flag_hash != 0 && flag_hash == HashSiteForLogBacktraceAt(file, line); +} + +} // namespace log_internal + +void SetLogBacktraceLocation(absl::string_view file, int line) { + log_backtrace_at_hash.store(HashSiteForLogBacktraceAt(file, line), + std::memory_order_release); +} + +bool ShouldPrependLogPrefix() { + return prepend_log_prefix.load(std::memory_order_acquire); +} + +void EnableLogPrefix(bool on_off) { + log_internal::RawEnableLogPrefix(on_off); + TriggerLoggingGlobalsListener(); +} + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/globals.h b/absl/log/globals.h new file mode 100644 index 00000000..32b87db0 --- /dev/null +++ b/absl/log/globals.h @@ -0,0 +1,165 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/globals.h +// ----------------------------------------------------------------------------- +// +// This header declares global logging library configuration knobs. + +#ifndef ABSL_LOG_GLOBALS_H_ +#define ABSL_LOG_GLOBALS_H_ + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +//------------------------------------------------------------------------------ +// Minimum Log Level +//------------------------------------------------------------------------------ +// +// Messages logged at or above this severity are directed to all registered log +// sinks or skipped otherwise. This parameter can also be modified using +// command line flag --minloglevel. +// See absl/base/log_severity.h for descriptions of severity levels. + +// MinLogLevel() +// +// Returns the value of the Minimum Log Level parameter. +// This function is async-signal-safe. +ABSL_MUST_USE_RESULT absl::LogSeverityAtLeast MinLogLevel(); + +// SetMinLogLevel() +// +// Updates the value of Minimum Log Level parameter. +// This function is async-signal-safe. +void SetMinLogLevel(absl::LogSeverityAtLeast severity); + +namespace log_internal { + +// ScopedMinLogLevel +// +// RAII type used to temporarily update the Min Log Level parameter. +class ScopedMinLogLevel final { + public: + explicit ScopedMinLogLevel(absl::LogSeverityAtLeast severity); + ScopedMinLogLevel(const ScopedMinLogLevel&) = delete; + ScopedMinLogLevel& operator=(const ScopedMinLogLevel&) = delete; + ~ScopedMinLogLevel(); + + private: + absl::LogSeverityAtLeast saved_severity_; +}; + +} // namespace log_internal + +//------------------------------------------------------------------------------ +// Stderr Threshold +//------------------------------------------------------------------------------ +// +// Messages logged at or above this level are directed to stderr in +// addition to other registered log sinks. This parameter can also be modified +// using command line flag --stderrthreshold. +// See absl/base/log_severity.h for descriptions of severity levels. + +// StderrThreshold() +// +// Returns the value of the Stderr Threshold parameter. +// This function is async-signal-safe. +ABSL_MUST_USE_RESULT absl::LogSeverityAtLeast StderrThreshold(); + +// SetStderrThreshold() +// +// Updates the Stderr Threshold parameter. +// This function is async-signal-safe. +void SetStderrThreshold(absl::LogSeverityAtLeast severity); +inline void SetStderrThreshold(absl::LogSeverity severity) { + absl::SetStderrThreshold(static_cast<absl::LogSeverityAtLeast>(severity)); +} + +// ScopedStderrThreshold +// +// RAII type used to temporarily update the Stderr Threshold parameter. +class ScopedStderrThreshold final { + public: + explicit ScopedStderrThreshold(absl::LogSeverityAtLeast severity); + ScopedStderrThreshold(const ScopedStderrThreshold&) = delete; + ScopedStderrThreshold& operator=(const ScopedStderrThreshold&) = delete; + ~ScopedStderrThreshold(); + + private: + absl::LogSeverityAtLeast saved_severity_; +}; + +//------------------------------------------------------------------------------ +// Log Backtrace At +//------------------------------------------------------------------------------ +// +// Users can request backtrace to be logged at specific locations, specified +// by file and line number. + +// ShouldLogBacktraceAt() +// +// Returns true if we should log a backtrace at the specified location. +namespace log_internal { +ABSL_MUST_USE_RESULT bool ShouldLogBacktraceAt(absl::string_view file, + int line); +} // namespace log_internal + +// SetLogBacktraceLocation() +// +// Sets the location the backtrace should be logged at. +void SetLogBacktraceLocation(absl::string_view file, int line); + +//------------------------------------------------------------------------------ +// Prepend Log Prefix +//------------------------------------------------------------------------------ +// +// This option tells the logging library that every logged message +// should include the prefix (severity, date, time, PID, etc.) + +// ShouldPrependLogPrefix() +// +// Returns the value of the Prepend Log Prefix option. +// This function is async-signal-safe. +ABSL_MUST_USE_RESULT bool ShouldPrependLogPrefix(); + +// EnableLogPrefix() +// +// Updates the value of the Prepend Log Prefix option. +// This function is async-signal-safe. +void EnableLogPrefix(bool on_off); + +namespace log_internal { + +using LoggingGlobalsListener = void (*)(); +void SetLoggingGlobalsListener(LoggingGlobalsListener l); + +// Internal implementation for the setter routines. These are used +// to break circular dependencies between flags and globals. Each "Raw" +// routine corresponds to the non-"Raw" counterpart and used to set the +// configuration parameter directly without calling back to the listener. +void RawSetMinLogLevel(absl::LogSeverityAtLeast severity); +void RawSetStderrThreshold(absl::LogSeverityAtLeast severity); +void RawEnableLogPrefix(bool on_off); + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_GLOBALS_H_ diff --git a/absl/log/globals_test.cc b/absl/log/globals_test.cc new file mode 100644 index 00000000..6710c5aa --- /dev/null +++ b/absl/log/globals_test.cc @@ -0,0 +1,91 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/globals.h" + +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/log_severity.h" +#include "absl/log/internal/globals.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/log.h" +#include "absl/log/scoped_mock_log.h" + +namespace { + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +constexpr static absl::LogSeverityAtLeast DefaultMinLogLevel() { + return absl::LogSeverityAtLeast::kInfo; +} +constexpr static absl::LogSeverityAtLeast DefaultStderrThreshold() { + return absl::LogSeverityAtLeast::kError; +} + +TEST(TestGlobals, MinLogLevel) { + EXPECT_EQ(absl::MinLogLevel(), DefaultMinLogLevel()); + absl::SetMinLogLevel(absl::LogSeverityAtLeast::kError); + EXPECT_EQ(absl::MinLogLevel(), absl::LogSeverityAtLeast::kError); + absl::SetMinLogLevel(DefaultMinLogLevel()); +} + +TEST(TestGlobals, ScopedMinLogLevel) { + EXPECT_EQ(absl::MinLogLevel(), DefaultMinLogLevel()); + { + absl::log_internal::ScopedMinLogLevel scoped_stderr_threshold( + absl::LogSeverityAtLeast::kError); + EXPECT_EQ(absl::MinLogLevel(), absl::LogSeverityAtLeast::kError); + } + EXPECT_EQ(absl::MinLogLevel(), DefaultMinLogLevel()); +} + +TEST(TestGlobals, StderrThreshold) { + EXPECT_EQ(absl::StderrThreshold(), DefaultStderrThreshold()); + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kError); + EXPECT_EQ(absl::StderrThreshold(), absl::LogSeverityAtLeast::kError); + absl::SetStderrThreshold(DefaultStderrThreshold()); +} + +TEST(TestGlobals, ScopedStderrThreshold) { + EXPECT_EQ(absl::StderrThreshold(), DefaultStderrThreshold()); + { + absl::ScopedStderrThreshold scoped_stderr_threshold( + absl::LogSeverityAtLeast::kError); + EXPECT_EQ(absl::StderrThreshold(), absl::LogSeverityAtLeast::kError); + } + EXPECT_EQ(absl::StderrThreshold(), DefaultStderrThreshold()); +} + +TEST(TestGlobals, LogBacktraceAt) { + EXPECT_FALSE(absl::log_internal::ShouldLogBacktraceAt("some_file.cc", 111)); + absl::SetLogBacktraceLocation("some_file.cc", 111); + EXPECT_TRUE(absl::log_internal::ShouldLogBacktraceAt("some_file.cc", 111)); + EXPECT_FALSE( + absl::log_internal::ShouldLogBacktraceAt("another_file.cc", 222)); +} + +TEST(TestGlobals, LogPrefix) { + EXPECT_TRUE(absl::ShouldPrependLogPrefix()); + absl::EnableLogPrefix(false); + EXPECT_FALSE(absl::ShouldPrependLogPrefix()); + absl::EnableLogPrefix(true); + EXPECT_TRUE(absl::ShouldPrependLogPrefix()); +} + +} // namespace diff --git a/absl/log/initialize.cc b/absl/log/initialize.cc new file mode 100644 index 00000000..a3f6d6c1 --- /dev/null +++ b/absl/log/initialize.cc @@ -0,0 +1,34 @@ +// Copyright 2022 The Abseil 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. + +#include "absl/log/initialize.h" + +#include "absl/base/config.h" +#include "absl/log/internal/globals.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +void InitializeLog() { + // This comes first since it is used by RAW_LOG. + absl::log_internal::SetTimeZone(absl::LocalTimeZone()); + + // Note that initialization is complete, so logs can now be sent to their + // proper destinations rather than stderr. + log_internal::SetInitialized(); +} + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/initialize.h b/absl/log/initialize.h new file mode 100644 index 00000000..f600eb60 --- /dev/null +++ b/absl/log/initialize.h @@ -0,0 +1,45 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/initialize.h +// ----------------------------------------------------------------------------- +// +// This header declares the Abseil Log initialization routine InitializeLog(). + +#ifndef ABSL_LOG_INITIALIZE_H_ +#define ABSL_LOG_INITIALIZE_H_ + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// InitializeLog() +// +// Initializes the Abseil logging library. +// +// Before this function is called, all log messages are directed only to stderr. +// After initialization is finished, log messages are directed to all registered +// `LogSink`s. +// +// It is an error to call this function twice. +// +// There is no corresponding function to shut down the logging library. +void InitializeLog(); + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INITIALIZE_H_ diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel new file mode 100644 index 00000000..c674f288 --- /dev/null +++ b/absl/log/internal/BUILD.bazel @@ -0,0 +1,302 @@ +# +# Copyright 2022 The Abseil 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. +# + +load( + "//absl:copts/configure_copts.bzl", + "ABSL_DEFAULT_COPTS", + "ABSL_DEFAULT_LINKOPTS", + "ABSL_TEST_COPTS", +) + +package(default_visibility = [ + "//absl/log:__pkg__", +]) + +licenses(["notice"]) + +cc_library( + name = "check_op", + srcs = ["check_op.cc"], + hdrs = ["check_op.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/log:__pkg__", + ], + deps = [ + ":nullguard", + ":nullstream", + ":strip", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/strings", + ], +) + +cc_library( + name = "conditions", + srcs = ["conditions.cc"], + hdrs = ["conditions.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":voidify", + "//absl/base", + "//absl/base:config", + "//absl/base:core_headers", + ], +) + +cc_library( + name = "config", + hdrs = ["config.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/log:__pkg__", + ], + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + ], +) + +cc_library( + name = "flags", + hdrs = ["flags.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/flags:flag", + ], +) + +cc_library( + name = "format", + srcs = ["log_format.cc"], + hdrs = ["log_format.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":config", + ":globals", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/strings", + "//absl/strings:str_format", + "//absl/time", + "//absl/types:span", + ], +) + +cc_library( + name = "globals", + srcs = ["globals.cc"], + hdrs = ["globals.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/log:__pkg__", + ], + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/base:raw_logging_internal", + "//absl/strings", + "//absl/time", + ], +) + +cc_library( + name = "log_message", + srcs = ["log_message.cc"], + hdrs = ["log_message.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/log:__pkg__", + ], + deps = [ + ":config", + ":format", + ":globals", + ":log_sink_set", + ":nullguard", + "//absl/base", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:errno_saver", + "//absl/base:log_severity", + "//absl/base:raw_logging_internal", + "//absl/base:strerror", + "//absl/container:inlined_vector", + "//absl/debugging:examine_stack", + "//absl/log:globals", + "//absl/log:log_entry", + "//absl/log:log_sink", + "//absl/log:log_sink_registry", + "//absl/memory", + "//absl/strings", + "//absl/strings:str_format", + "//absl/time", + "//absl/types:span", + ], +) + +cc_library( + name = "log_sink_set", + srcs = ["log_sink_set.cc"], + hdrs = ["log_sink_set.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":config", + ":globals", + "//absl/base", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/base:raw_logging_internal", + "//absl/cleanup", + "//absl/log:globals", + "//absl/log:log_entry", + "//absl/log:log_sink", + "//absl/strings", + "//absl/synchronization", + "//absl/types:span", + ], +) + +cc_library( + name = "nullguard", + hdrs = ["nullguard.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + ], +) + +cc_library( + name = "nullstream", + hdrs = ["nullstream.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/strings", + ], +) + +cc_library( + name = "strip", + hdrs = ["strip.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log_message", + ":nullstream", + "//absl/base:log_severity", + ], +) + +cc_library( + name = "test_actions", + testonly = True, + srcs = ["test_actions.cc"], + hdrs = ["test_actions.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":config", + "//absl/base:config", + "//absl/base:log_severity", + "//absl/log:log_entry", + "//absl/strings", + "//absl/time", + ], +) + +cc_library( + name = "test_helpers", + testonly = True, + srcs = ["test_helpers.cc"], + hdrs = ["test_helpers.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":globals", + "//absl/base:config", + "//absl/base:log_severity", + "//absl/log:globals", + "//absl/log:initialize", + "@com_google_googletest//:gtest", + ], +) + +cc_library( + name = "test_matchers", + testonly = True, + srcs = ["test_matchers.cc"], + hdrs = ["test_matchers.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":config", + ":test_helpers", + "//absl/base:config", + "//absl/base:log_severity", + "//absl/log:log_entry", + "//absl/strings", + "//absl/time", + "@com_google_googletest//:gtest", + ], +) + +cc_library( + name = "voidify", + hdrs = ["voidify.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = ["//absl/base:config"], +) + +# Test targets +cc_test( + name = "stderr_log_sink_test", + size = "small", + srcs = ["stderr_log_sink_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "no_test_android", + "no_test_darwin_x86_64", + "no_test_ios", + "no_test_wasm", + ], + deps = [ + ":test_helpers", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/log", + "//absl/log:globals", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/absl/log/internal/check_op.cc b/absl/log/internal/check_op.cc new file mode 100644 index 00000000..f4b67647 --- /dev/null +++ b/absl/log/internal/check_op.cc @@ -0,0 +1,118 @@ +// Copyright 2022 The Abseil 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. + +#include "absl/log/internal/check_op.h" + +#include <string.h> + +#ifdef _MSC_VER +#define strcasecmp _stricmp +#else +#include <strings.h> // for strcasecmp, but msvc does not have this header +#endif + +#include <sstream> +#include <string> + +#include "absl/base/config.h" +#include "absl/strings/str_cat.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +#define ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(x) \ + template std::string* MakeCheckOpString(x, x, const char*) +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(bool); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(int64_t); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(uint64_t); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(float); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(double); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(char); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(unsigned char); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const std::string&); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const absl::string_view&); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const char*); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const signed char*); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const unsigned char*); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const void*); +#undef ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING + +CheckOpMessageBuilder::CheckOpMessageBuilder(const char* exprtext) { + stream_ << exprtext << " ("; +} + +std::ostream& CheckOpMessageBuilder::ForVar2() { + stream_ << " vs. "; + return stream_; +} + +std::string* CheckOpMessageBuilder::NewString() { + stream_ << ")"; + return new std::string(stream_.str()); +} + +void MakeCheckOpValueString(std::ostream& os, const char v) { + if (v >= 32 && v <= 126) { + os << "'" << v << "'"; + } else { + os << "char value " << int{v}; + } +} + +void MakeCheckOpValueString(std::ostream& os, const signed char v) { + if (v >= 32 && v <= 126) { + os << "'" << v << "'"; + } else { + os << "signed char value " << int{v}; + } +} + +void MakeCheckOpValueString(std::ostream& os, const unsigned char v) { + if (v >= 32 && v <= 126) { + os << "'" << v << "'"; + } else { + os << "unsigned char value " << int{v}; + } +} + +void MakeCheckOpValueString(std::ostream& os, const void* p) { + if (p == nullptr) { + os << "(null)"; + } else { + os << p; + } +} + +// Helper functions for string comparisons. +#define DEFINE_CHECK_STROP_IMPL(name, func, expected) \ + std::string* Check##func##expected##Impl(const char* s1, const char* s2, \ + const char* exprtext) { \ + bool equal = s1 == s2 || (s1 && s2 && !func(s1, s2)); \ + if (equal == expected) { \ + return nullptr; \ + } else { \ + return new std::string( \ + absl::StrCat(exprtext, " (", s1, " vs. ", s2, ")")); \ + } \ + } +DEFINE_CHECK_STROP_IMPL(CHECK_STREQ, strcmp, true) +DEFINE_CHECK_STROP_IMPL(CHECK_STRNE, strcmp, false) +DEFINE_CHECK_STROP_IMPL(CHECK_STRCASEEQ, strcasecmp, true) +DEFINE_CHECK_STROP_IMPL(CHECK_STRCASENE, strcasecmp, false) +#undef DEFINE_CHECK_STROP_IMPL + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/check_op.h b/absl/log/internal/check_op.h new file mode 100644 index 00000000..559e5afc --- /dev/null +++ b/absl/log/internal/check_op.h @@ -0,0 +1,385 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/check_op.h +// ----------------------------------------------------------------------------- +// +// This file declares helpers routines and macros used to implement `CHECK` +// macros. + +#ifndef ABSL_LOG_INTERNAL_CHECK_OP_H_ +#define ABSL_LOG_INTERNAL_CHECK_OP_H_ + +#include <stdint.h> + +#include <ostream> +#include <sstream> +#include <string> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/optimization.h" +#include "absl/log/internal/nullguard.h" +#include "absl/log/internal/nullstream.h" +#include "absl/log/internal/strip.h" + +// `ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL` wraps string literals that +// should be stripped when `ABSL_MIN_LOG_LEVEL` exceeds `kFatal`. +#ifdef ABSL_MIN_LOG_LEVEL +#define ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(literal) \ + (::absl::LogSeverity::kFatal >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) \ + ? (literal) \ + : "") +#else +#define ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(literal) (literal) +#endif + +#ifdef NDEBUG +// `NDEBUG` is defined, so `DCHECK_EQ(x, y)` and so on do nothing. However, we +// still want the compiler to parse `x` and `y`, because we don't want to lose +// potentially useful errors and warnings. +#define ABSL_LOG_INTERNAL_DCHECK_NOP(x, y) \ + while (false && ((void)(x), (void)(y), 0)) \ + ::absl::log_internal::NullStream().InternalStream() +#endif + +#define ABSL_LOG_INTERNAL_CHECK_OP(name, op, val1, val2) \ + while ( \ + ::std::string* absl_log_internal_check_op_result ABSL_ATTRIBUTE_UNUSED = \ + ::absl::log_internal::name##Impl( \ + ::absl::log_internal::GetReferenceableValue(val1), \ + ::absl::log_internal::GetReferenceableValue(val2), \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(#val1 " " #op \ + " " #val2))) \ + ABSL_LOG_INTERNAL_CHECK(*absl_log_internal_check_op_result).InternalStream() +#define ABSL_LOG_INTERNAL_QCHECK_OP(name, op, val1, val2) \ + while (::std::string* absl_log_internal_qcheck_op_result = \ + ::absl::log_internal::name##Impl( \ + ::absl::log_internal::GetReferenceableValue(val1), \ + ::absl::log_internal::GetReferenceableValue(val2), \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(#val1 " " #op \ + " " #val2))) \ + ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_qcheck_op_result).InternalStream() +#define ABSL_LOG_INTERNAL_CHECK_STROP(func, op, expected, s1, s2) \ + while (::std::string* absl_log_internal_check_strop_result = \ + ::absl::log_internal::Check##func##expected##Impl( \ + (s1), (s2), \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(#s1 " " #op " " #s2))) \ + ABSL_LOG_INTERNAL_CHECK(*absl_log_internal_check_strop_result) \ + .InternalStream() +#define ABSL_LOG_INTERNAL_QCHECK_STROP(func, op, expected, s1, s2) \ + while (::std::string* absl_log_internal_qcheck_strop_result = \ + ::absl::log_internal::Check##func##expected##Impl( \ + (s1), (s2), \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(#s1 " " #op " " #s2))) \ + ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_qcheck_strop_result) \ + .InternalStream() +// This one is tricky: +// * We must evaluate `val` exactly once, yet we need to do two things with it: +// evaluate `.ok()` and (sometimes) `.ToString()`. +// * `val` might be an `absl::Status` or some `absl::StatusOr<T>`. +// * `val` might be e.g. `ATemporary().GetStatus()`, which may return a +// reference to a member of `ATemporary` that is only valid until the end of +// the full expression. +// * We don't want this file to depend on `absl::Status` `#include`s or linkage, +// nor do we want to move the definition to status and introduce a dependency +// in the other direction. We can be assured that callers must already have a +// `Status` and the necessary `#include`s and linkage. +// * Callsites should be small and fast (at least when `val.ok()`): one branch, +// minimal stack footprint. +// * In particular, the string concat stuff should be out-of-line and emitted +// in only one TU to save linker input size +// * We want the `val.ok()` check inline so static analyzers and optimizers can +// see it. +// * As usual, no braces so we can stream into the expansion with `operator<<`. +// * Also as usual, it must expand to a single (partial) statement with no +// ambiguous-else problems. +#define ABSL_LOG_INTERNAL_CHECK_OK(val) \ + for (::std::pair<const ::absl::Status*, ::std::string*> \ + absl_log_internal_check_ok_goo; \ + absl_log_internal_check_ok_goo.first = \ + ::absl::log_internal::AsStatus(val), \ + absl_log_internal_check_ok_goo.second = \ + ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok()) \ + ? nullptr \ + : ::absl::status_internal::MakeCheckFailString( \ + absl_log_internal_check_ok_goo.first, \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(#val " is OK")), \ + !ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok());) \ + ABSL_LOG_INTERNAL_CHECK(*absl_log_internal_check_ok_goo.second) \ + .InternalStream() +#define ABSL_LOG_INTERNAL_QCHECK_OK(val) \ + for (::std::pair<const ::absl::Status*, ::std::string*> \ + absl_log_internal_check_ok_goo; \ + absl_log_internal_check_ok_goo.first = \ + ::absl::log_internal::AsStatus(val), \ + absl_log_internal_check_ok_goo.second = \ + ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok()) \ + ? nullptr \ + : ::absl::status_internal::MakeCheckFailString( \ + absl_log_internal_check_ok_goo.first, \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(#val " is OK")), \ + !ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok());) \ + ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_check_ok_goo.second) \ + .InternalStream() + +namespace absl { +ABSL_NAMESPACE_BEGIN + +class Status; +template <typename T> +class StatusOr; + +namespace status_internal { +std::string* MakeCheckFailString(const absl::Status* status, + const char* prefix); +} // namespace status_internal + +namespace log_internal { + +// Convert a Status or a StatusOr to its underlying status value. +// +// (This implementation does not require a dep on absl::Status to work.) +inline const absl::Status* AsStatus(const absl::Status& s) { return &s; } +template <typename T> +const absl::Status* AsStatus(const absl::StatusOr<T>& s) { + return &s.status(); +} + +// A helper class for formatting `expr (V1 vs. V2)` in a `CHECK_XX` statement. +// See `MakeCheckOpString` for sample usage. +class CheckOpMessageBuilder final { + public: + // Inserts `exprtext` and ` (` to the stream. + explicit CheckOpMessageBuilder(const char* exprtext); + ~CheckOpMessageBuilder() = default; + // For inserting the first variable. + std::ostream& ForVar1() { return stream_; } + // For inserting the second variable (adds an intermediate ` vs. `). + std::ostream& ForVar2(); + // Get the result (inserts the closing `)`). + std::string* NewString(); + + private: + std::ostringstream stream_; +}; + +// This formats a value for a failing `CHECK_XX` statement. Ordinarily, it uses +// the definition for `operator<<`, with a few special cases below. +template <typename T> +inline void MakeCheckOpValueString(std::ostream& os, const T& v) { + os << log_internal::NullGuard<T>::Guard(v); +} + +// Overloads for char types provide readable values for unprintable characters. +void MakeCheckOpValueString(std::ostream& os, char v); +void MakeCheckOpValueString(std::ostream& os, signed char v); +void MakeCheckOpValueString(std::ostream& os, unsigned char v); +void MakeCheckOpValueString(std::ostream& os, const void* p); + +namespace detect_specialization { + +// MakeCheckOpString is being specialized for every T and U pair that is being +// passed to the CHECK_op macros. However, there is a lot of redundancy in these +// specializations that creates unnecessary library and binary bloat. +// The number of instantiations tends to be O(n^2) because we have two +// independent inputs. This technique works by reducing `n`. +// +// Most user-defined types being passed to CHECK_op end up being printed as a +// builtin type. For example, enums tend to be implicitly converted to its +// underlying type when calling operator<<, and pointers are printed with the +// `const void*` overload. +// To reduce the number of instantiations we coerce these values before calling +// MakeCheckOpString instead of inside it. +// +// To detect if this coercion is needed, we duplicate all the relevant +// operator<< overloads as specified in the standard, just in a different +// namespace. If the call to `stream << value` becomes ambiguous, it means that +// one of these overloads is the one selected by overload resolution. We then +// do overload resolution again just with our overload set to see which one gets +// selected. That tells us which type to coerce to. +// If the augmented call was not ambiguous, it means that none of these were +// selected and we can't coerce the input. +// +// As a secondary step to reduce code duplication, we promote integral types to +// their 64-bit variant. This does not change the printed value, but reduces the +// number of instantiations even further. Promoting an integer is very cheap at +// the call site. +int64_t operator<<(std::ostream&, short value); // NOLINT +int64_t operator<<(std::ostream&, unsigned short value); // NOLINT +int64_t operator<<(std::ostream&, int value); +int64_t operator<<(std::ostream&, unsigned int value); +int64_t operator<<(std::ostream&, long value); // NOLINT +uint64_t operator<<(std::ostream&, unsigned long value); // NOLINT +int64_t operator<<(std::ostream&, long long value); // NOLINT +uint64_t operator<<(std::ostream&, unsigned long long value); // NOLINT +float operator<<(std::ostream&, float value); +double operator<<(std::ostream&, double value); +long double operator<<(std::ostream&, long double value); +bool operator<<(std::ostream&, bool value); +const void* operator<<(std::ostream&, const void* value); +const void* operator<<(std::ostream&, std::nullptr_t); + +// These `char` overloads are specified like this in the standard, so we have to +// write them exactly the same to ensure the call is ambiguous. +// If we wrote it in a different way (eg taking std::ostream instead of the +// template) then one call might have a higher rank than the other and it would +// not be ambiguous. +template <typename Traits> +char operator<<(std::basic_ostream<char, Traits>&, char); +template <typename Traits> +signed char operator<<(std::basic_ostream<char, Traits>&, signed char); +template <typename Traits> +unsigned char operator<<(std::basic_ostream<char, Traits>&, unsigned char); +template <typename Traits> +const char* operator<<(std::basic_ostream<char, Traits>&, const char*); +template <typename Traits> +const signed char* operator<<(std::basic_ostream<char, Traits>&, + const signed char*); +template <typename Traits> +const unsigned char* operator<<(std::basic_ostream<char, Traits>&, + const unsigned char*); + +// This overload triggers when the call is not ambiguous. +// It means that T is being printed with some overload not on this list. +// We keep the value as `const T&`. +template <typename T, typename = decltype(std::declval<std::ostream&>() + << std::declval<const T&>())> +const T& Detect(int); + +// This overload triggers when the call is ambiguous. +// It means that T is either one from this list or printed as one from this +// list. Eg an enum that decays to `int` for printing. +// We ask the overload set to give us the type we want to convert it to. +template <typename T> +decltype(detect_specialization::operator<<(std::declval<std::ostream&>(), + std::declval<const T&>())) +Detect(char); + +} // namespace detect_specialization + +template <typename T> +using CheckOpStreamType = decltype(detect_specialization::Detect<T>(0)); + +// Build the error message string. Specify no inlining for code size. +template <typename T1, typename T2> +ABSL_ATTRIBUTE_RETURNS_NONNULL std::string* MakeCheckOpString( + T1 v1, T2 v2, const char* exprtext) ABSL_ATTRIBUTE_NOINLINE; + +template <typename T1, typename T2> +std::string* MakeCheckOpString(T1 v1, T2 v2, const char* exprtext) { + CheckOpMessageBuilder comb(exprtext); + MakeCheckOpValueString(comb.ForVar1(), v1); + MakeCheckOpValueString(comb.ForVar2(), v2); + return comb.NewString(); +} + +// Add a few commonly used instantiations as extern to reduce size of objects +// files. +#define ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(x) \ + extern template std::string* MakeCheckOpString(x, x, const char*) +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(bool); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(int64_t); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(uint64_t); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(float); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(double); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(char); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(unsigned char); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const std::string&); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const absl::string_view&); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const char*); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const signed char*); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const unsigned char*); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const void*); +#undef ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN + +// Helper functions for `ABSL_LOG_INTERNAL_CHECK_OP` macro family. The +// `(int, int)` override works around the issue that the compiler will not +// instantiate the template version of the function on values of unnamed enum +// type. +#define ABSL_LOG_INTERNAL_CHECK_OP_IMPL(name, op) \ + template <typename T1, typename T2> \ + inline constexpr ::std::string* name##Impl(const T1& v1, const T2& v2, \ + const char* exprtext) { \ + using U1 = CheckOpStreamType<T1>; \ + using U2 = CheckOpStreamType<T2>; \ + return ABSL_PREDICT_TRUE(v1 op v2) \ + ? nullptr \ + : MakeCheckOpString<U1, U2>(v1, v2, exprtext); \ + } \ + inline constexpr ::std::string* name##Impl(int v1, int v2, \ + const char* exprtext) { \ + return name##Impl<int, int>(v1, v2, exprtext); \ + } + +ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_EQ, ==) +ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_NE, !=) +ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_LE, <=) +ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_LT, <) +ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_GE, >=) +ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_GT, >) +#undef ABSL_LOG_INTERNAL_CHECK_OP_IMPL + +std::string* CheckstrcmptrueImpl(const char* s1, const char* s2, + const char* exprtext); +std::string* CheckstrcmpfalseImpl(const char* s1, const char* s2, + const char* exprtext); +std::string* CheckstrcasecmptrueImpl(const char* s1, const char* s2, + const char* exprtext); +std::string* CheckstrcasecmpfalseImpl(const char* s1, const char* s2, + const char* exprtext); + +// `CHECK_EQ` and friends want to pass their arguments by reference, however +// this winds up exposing lots of cases where people have defined and +// initialized static const data members but never declared them (i.e. in a .cc +// file), meaning they are not referenceable. This function avoids that problem +// for integers (the most common cases) by overloading for every primitive +// integer type, even the ones we discourage, and returning them by value. +template <typename T> +inline constexpr const T& GetReferenceableValue(const T& t) { + return t; +} +inline constexpr char GetReferenceableValue(char t) { return t; } +inline constexpr unsigned char GetReferenceableValue(unsigned char t) { + return t; +} +inline constexpr signed char GetReferenceableValue(signed char t) { return t; } +inline constexpr short GetReferenceableValue(short t) { return t; } // NOLINT +inline constexpr unsigned short GetReferenceableValue( // NOLINT + unsigned short t) { // NOLINT + return t; +} +inline constexpr int GetReferenceableValue(int t) { return t; } +inline unsigned int GetReferenceableValue(unsigned int t) { return t; } +inline constexpr long GetReferenceableValue(long t) { return t; } // NOLINT +inline constexpr unsigned long GetReferenceableValue( // NOLINT + unsigned long t) { // NOLINT + return t; +} +inline constexpr long long GetReferenceableValue(long long t) { // NOLINT + return t; +} +inline constexpr unsigned long long GetReferenceableValue( // NOLINT + unsigned long long t) { // NOLINT + return t; +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_CHECK_OP_H_ diff --git a/absl/log/internal/conditions.cc b/absl/log/internal/conditions.cc new file mode 100644 index 00000000..70f2acef --- /dev/null +++ b/absl/log/internal/conditions.cc @@ -0,0 +1,83 @@ +// Copyright 2022 The Abseil 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. + +#include "absl/log/internal/conditions.h" + +#include <atomic> +#include <cstdint> + +#include "absl/base/config.h" +#include "absl/base/internal/cycleclock.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +namespace { + +// The following code behaves like AtomicStatsCounter::LossyAdd() for +// speed since it is fine to lose occasional updates. +// Returns old value of *counter. +uint32_t LossyIncrement(std::atomic<uint32_t>* counter) { + const uint32_t value = counter->load(std::memory_order_relaxed); + counter->store(value + 1, std::memory_order_relaxed); + return value; +} + +} // namespace + +bool LogEveryNState::ShouldLog(int n) { + return n != 0 && (LossyIncrement(&counter_) % n) == 0; +} + +bool LogFirstNState::ShouldLog(int n) { + const uint32_t counter_value = counter_.load(std::memory_order_relaxed); + if (static_cast<int64_t>(counter_value) < n) { + counter_.store(counter_value + 1, std::memory_order_relaxed); + return true; + } + return false; +} + +bool LogEveryPow2State::ShouldLog() { + const uint32_t new_value = LossyIncrement(&counter_) + 1; + return (new_value & (new_value - 1)) == 0; +} + +bool LogEveryNSecState::ShouldLog(double seconds) { + using absl::base_internal::CycleClock; + LossyIncrement(&counter_); + const int64_t now_cycles = CycleClock::Now(); + int64_t next_cycles = next_log_time_cycles_.load(std::memory_order_relaxed); +#if defined(__myriad2__) + // myriad2 does not have 8-byte compare and exchange. Use a racy version that + // is "good enough" but will over-log in the face of concurrent logging. + if (now_cycles > next_cycles) { + next_log_time_cycles_.store(now_cycles + seconds * CycleClock::Frequency(), + std::memory_order_relaxed); + return true; + } + return false; +#else + do { + if (now_cycles <= next_cycles) return false; + } while (!next_log_time_cycles_.compare_exchange_weak( + next_cycles, now_cycles + seconds * CycleClock::Frequency(), + std::memory_order_relaxed, std::memory_order_relaxed)); + return true; +#endif +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/conditions.h b/absl/log/internal/conditions.h new file mode 100644 index 00000000..b89f1dfd --- /dev/null +++ b/absl/log/internal/conditions.h @@ -0,0 +1,222 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/conditions.h +// ----------------------------------------------------------------------------- +// +// This file contains implementation of conditional log statements, like LOG_IF +// including all the ABSL_LOG_INTERNAL_..._CONDITION_... macros and +// various condition classes like LogEveryNState. + +#ifndef ABSL_LOG_INTERNAL_CONDITIONS_H_ +#define ABSL_LOG_INTERNAL_CONDITIONS_H_ + +#ifdef _WIN32 +#include <cstdlib> +#else +#include <unistd.h> +#endif +#include <stdlib.h> + +#include <atomic> +#include <cstdint> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/log/internal/voidify.h" + +// `ABSL_LOG_INTERNAL_CONDITION` prefixes another macro that expands to a +// temporary `LogMessage` instantiation followed by zero or more streamed +// expressions. This definition is tricky to read correctly. It evaluates to +// either +// +// (void)0; +// +// or +// +// ::absl::log_internal::Voidify() && +// ::absl::log_internal::LogMessage(...) << "the user's message"; +// +// If the condition is evaluable at compile time, as is often the case, it +// compiles away to just one side or the other. +// +// Although this is not used anywhere a statement (e.g. `if`) could not go, +// the ternary expression does a better job avoiding spurious diagnostics +// (dangling else, missing switch case) and preserving noreturn semantics (e.g. +// on `LOG(FATAL)`) without requiring braces. +#define ABSL_LOG_INTERNAL_STATELESS_CONDITION(condition) \ + switch (0) \ + case 0: \ + !(condition) ? (void)0 : ::absl::log_internal::Voidify()&& + +// `ABSL_LOG_INTERNAL_STATEFUL_CONDITION` applies a condition like +// `ABSL_LOG_INTERNAL_CONDITION` but adds to that a series of variable +// declarations, including a local static object which stores the state needed +// to implement the stateful macros like `LOG_EVERY_N`. +// +// `for`-loops are used to declare scoped variables without braces (to permit +// streaming into the macro's expansion) and without the dangling-`else` +// problems/diagnostics that come with `if`. +// +// Two more variables are declared in separate `for`-loops: +// +// * `COUNTER` implements a streamable token whose value when streamed is the +// number of times execution has passed through the macro. +// * A boolean flag is used to prevent any of the `for`-loops from ever actually +// looping. +#define ABSL_LOG_INTERNAL_STATEFUL_CONDITION(condition) \ + for (bool absl_log_internal_stateful_condition_do_log(condition); \ + absl_log_internal_stateful_condition_do_log; \ + absl_log_internal_stateful_condition_do_log = false) \ + ABSL_LOG_INTERNAL_STATEFUL_CONDITION_IMPL +#define ABSL_LOG_INTERNAL_STATEFUL_CONDITION_IMPL(kind, ...) \ + for (static ::absl::log_internal::Log##kind##State \ + absl_log_internal_stateful_condition_state; \ + absl_log_internal_stateful_condition_do_log && \ + absl_log_internal_stateful_condition_state.ShouldLog(__VA_ARGS__); \ + absl_log_internal_stateful_condition_do_log = false) \ + for (const uint32_t COUNTER ABSL_ATTRIBUTE_UNUSED = \ + absl_log_internal_stateful_condition_state.counter(); \ + absl_log_internal_stateful_condition_do_log; \ + absl_log_internal_stateful_condition_do_log = false) + +// `ABSL_LOG_INTERNAL_CONDITION_*` serve to combine any conditions from the +// macro (e.g. `LOG_IF` or `VLOG`) with inherent conditions (e.g. +// `ABSL_MIN_LOG_LEVEL`) into a single boolean expression. We could chain +// ternary operators instead, however some versions of Clang sometimes issue +// spurious diagnostics after such expressions due to a control flow analysis +// bug. +#ifdef ABSL_MIN_LOG_LEVEL +#define ABSL_LOG_INTERNAL_CONDITION_INFO(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + (condition) && ::absl::LogSeverity::kInfo >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL)) +#define ABSL_LOG_INTERNAL_CONDITION_WARNING(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + (condition) && ::absl::LogSeverity::kWarning >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL)) +#define ABSL_LOG_INTERNAL_CONDITION_ERROR(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + (condition) && ::absl::LogSeverity::kError >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL)) +// NOTE: Use ternary operators instead of short-circuiting to mitigate +// https://bugs.llvm.org/show_bug.cgi?id=51928. +#define ABSL_LOG_INTERNAL_CONDITION_FATAL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + ((condition) \ + ? (::absl::LogSeverity::kFatal >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) \ + ? true \ + : (::absl::log_internal::AbortQuietly(), false)) \ + : false)) +// NOTE: Use ternary operators instead of short-circuiting to mitigate +// https://bugs.llvm.org/show_bug.cgi?id=51928. +#define ABSL_LOG_INTERNAL_CONDITION_QFATAL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + ((condition) \ + ? (::absl::LogSeverity::kFatal >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) \ + ? true \ + : (::absl::log_internal::ExitQuietly(), false)) \ + : false)) + +#define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \ + for (int log_internal_severity_loop = 1; log_internal_severity_loop; \ + log_internal_severity_loop = 0) \ + for (const absl::LogSeverity log_internal_severity = \ + ::absl::NormalizeLogSeverity(severity); \ + log_internal_severity_loop; log_internal_severity_loop = 0) \ + ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL +#define ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + (condition) && \ + (log_internal_severity >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) || \ + (log_internal_severity == ::absl::LogSeverity::kFatal && \ + (::absl::log_internal::AbortQuietly(), false)))) +#else // ndef ABSL_MIN_LOG_LEVEL +#define ABSL_LOG_INTERNAL_CONDITION_INFO(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION(condition) +#define ABSL_LOG_INTERNAL_CONDITION_WARNING(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION(condition) +#define ABSL_LOG_INTERNAL_CONDITION_ERROR(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION(condition) +#define ABSL_LOG_INTERNAL_CONDITION_FATAL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION(condition) +#define ABSL_LOG_INTERNAL_CONDITION_QFATAL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION(condition) +#define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \ + for (int log_internal_severity_loop = 1; log_internal_severity_loop; \ + log_internal_severity_loop = 0) \ + for (const absl::LogSeverity log_internal_severity = \ + ::absl::NormalizeLogSeverity(severity); \ + log_internal_severity_loop; log_internal_severity_loop = 0) \ + ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL +#define ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION(condition) +#endif // ndef ABSL_MIN_LOG_LEVEL + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// Stateful condition class name should be "Log" + name + "State". +class LogEveryNState final { + public: + bool ShouldLog(int n); + uint32_t counter() { return counter_.load(std::memory_order_relaxed); } + + private: + std::atomic<uint32_t> counter_{0}; +}; + +class LogFirstNState final { + public: + bool ShouldLog(int n); + uint32_t counter() { return counter_.load(std::memory_order_relaxed); } + + private: + std::atomic<uint32_t> counter_{0}; +}; + +class LogEveryPow2State final { + public: + bool ShouldLog(); + uint32_t counter() { return counter_.load(std::memory_order_relaxed); } + + private: + std::atomic<uint32_t> counter_{0}; +}; + +class LogEveryNSecState final { + public: + bool ShouldLog(double seconds); + uint32_t counter() { return counter_.load(std::memory_order_relaxed); } + + private: + std::atomic<uint32_t> counter_{0}; + // Cycle count according to CycleClock that we should next log at. + std::atomic<int64_t> next_log_time_cycles_{0}; +}; + +// Helper routines to abort the application quietly + +ABSL_ATTRIBUTE_NORETURN inline void AbortQuietly() { abort(); } +ABSL_ATTRIBUTE_NORETURN inline void ExitQuietly() { _exit(1); } +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_CONDITIONS_H_ diff --git a/absl/log/internal/config.h b/absl/log/internal/config.h new file mode 100644 index 00000000..379e9ab9 --- /dev/null +++ b/absl/log/internal/config.h @@ -0,0 +1,45 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/config.h +// ----------------------------------------------------------------------------- +// + +#ifndef ABSL_LOG_INTERNAL_CONFIG_H_ +#define ABSL_LOG_INTERNAL_CONFIG_H_ + +#include "absl/base/config.h" + +#ifdef _WIN32 +#include <cstdint> +#else +#include <sys/types.h> +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +#ifdef _WIN32 +using Tid = uint32_t; +#else +using Tid = pid_t; +#endif + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_CONFIG_H_ diff --git a/absl/log/internal/flags.h b/absl/log/internal/flags.h new file mode 100644 index 00000000..0c5e81ed --- /dev/null +++ b/absl/log/internal/flags.h @@ -0,0 +1,53 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/log_flags.h +// ----------------------------------------------------------------------------- +// +// This header declares set of flags which can be used to configure Abseil +// Logging library behaviour at runtime. + +#ifndef ABSL_LOG_INTERNAL_FLAGS_H_ +#define ABSL_LOG_INTERNAL_FLAGS_H_ + +#include <string> + +#include "absl/flags/declare.h" + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// These flags should not be used in C++ code to access logging library +// configuration knobs. Use interfaces defined in absl/log/globals.h +// instead. It is still ok to use these flags on a command line. +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +// Log messages at this severity or above are sent to stderr in *addition* to +// logfiles. Defaults to `ERROR`. See log_severity.h for numeric values of +// severity levels. +ABSL_DECLARE_FLAG(int, stderrthreshold); + +// Log messages at this severity or above are logged; others are discarded. +// Defaults to `INFO`, i.e. log all severities. See log_severity.h for numeric +// values of severity levels. +ABSL_DECLARE_FLAG(int, minloglevel); + +// If specified in the form file:linenum, any messages logged from a matching +// location will also include a backtrace. +ABSL_DECLARE_FLAG(std::string, log_backtrace_at); + +// If true, the log prefix (severity, date, time, PID, etc.) is prepended to +// each message logged. Defaults to true. +ABSL_DECLARE_FLAG(bool, log_prefix); + +#endif // ABSL_LOG_INTERNAL_FLAGS_H_ diff --git a/absl/log/internal/globals.cc b/absl/log/internal/globals.cc new file mode 100644 index 00000000..863b047f --- /dev/null +++ b/absl/log/internal/globals.cc @@ -0,0 +1,125 @@ +// Copyright 2022 The Abseil 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. + +#include "absl/log/internal/globals.h" + +#include <atomic> +#include <cstdio> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/base/log_severity.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +namespace { +// Keeps track of whether Logging initialization is finalized. +// Log messages generated before that will go to stderr. +ABSL_CONST_INIT std::atomic<bool> logging_initialized(false); + +// The TimeZone used for logging. This may only be set once. +ABSL_CONST_INIT std::atomic<absl::TimeZone*> timezone_ptr{nullptr}; + +// If true, the logging library will symbolize stack in fatal messages +ABSL_CONST_INIT std::atomic<bool> symbolize_stack_trace(true); + +// Specifies maximum number of stack frames to report in fatal messages. +ABSL_CONST_INIT std::atomic<int> max_frames_in_stack_trace(64); + +ABSL_CONST_INIT std::atomic<bool> exit_on_dfatal(true); +ABSL_CONST_INIT std::atomic<bool> suppress_sigabort_trace(false); +} // namespace + +bool IsInitialized() { + return logging_initialized.load(std::memory_order_acquire); +} + +void SetInitialized() { + logging_initialized.store(true, std::memory_order_release); +} + +void WriteToStderr(absl::string_view message, absl::LogSeverity severity) { + // Avoid using std::cerr from this module since we may get called during + // exit code, and cerr may be partially or fully destroyed by then. + std::fwrite(message.data(), message.size(), 1, stderr); + +#if defined(_WIN64) || defined(_WIN32) || defined(_WIN16) + // C99 requires stderr to not be fully-buffered by default (7.19.3.7), but + // MS CRT buffers it anyway, so we must `fflush` to ensure the string hits + // the console/file before the program dies (and takes the libc buffers + // with it). + // https://docs.microsoft.com/en-us/cpp/c-runtime-library/stream-i-o + if (severity >= absl::LogSeverity::kWarning) { + std::fflush(stderr); + } +#else + // Avoid unused parameter warning in this branch. + (void)severity; +#endif +} + +void SetTimeZone(absl::TimeZone tz) { + absl::TimeZone* expected = nullptr; + absl::TimeZone* new_tz = new absl::TimeZone(tz); + // timezone_ptr can only be set once, otherwise new_tz is leaked. + if (!timezone_ptr.compare_exchange_strong(expected, new_tz, + std::memory_order_release, + std::memory_order_relaxed)) { + ABSL_RAW_LOG(FATAL, + "absl::log_internal::SetTimeZone() has already been called"); + } +} + +const absl::TimeZone* TimeZone() { + return timezone_ptr.load(std::memory_order_acquire); +} + +bool ShouldSymbolizeLogStackTrace() { + return symbolize_stack_trace.load(std::memory_order_acquire); +} + +void EnableSymbolizeLogStackTrace(bool on_off) { + symbolize_stack_trace.store(on_off, std::memory_order_release); +} + +int MaxFramesInLogStackTrace() { + return max_frames_in_stack_trace.load(std::memory_order_acquire); +} + +void SetMaxFramesInLogStackTrace(int max_num_frames) { + max_frames_in_stack_trace.store(max_num_frames, std::memory_order_release); +} + +bool ExitOnDFatal() { return exit_on_dfatal.load(std::memory_order_acquire); } + +void SetExitOnDFatal(bool on_off) { + exit_on_dfatal.store(on_off, std::memory_order_release); +} + +bool SuppressSigabortTrace() { + return suppress_sigabort_trace.load(std::memory_order_acquire); +} + +bool SetSuppressSigabortTrace(bool on_off) { + return suppress_sigabort_trace.exchange(on_off); +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/globals.h b/absl/log/internal/globals.h new file mode 100644 index 00000000..27bc0d09 --- /dev/null +++ b/absl/log/internal/globals.h @@ -0,0 +1,101 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/globals.h +// ----------------------------------------------------------------------------- +// +// This header file contains various global objects and static helper routines +// use in logging implementation. + +#ifndef ABSL_LOG_INTERNAL_GLOBALS_H_ +#define ABSL_LOG_INTERNAL_GLOBALS_H_ + +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// IsInitialized returns true if the logging library is initialized. +// This function is async-signal-safe +bool IsInitialized(); + +// SetLoggingInitialized is called once after logging initialization is done. +void SetInitialized(); + +// Unconditionally write a `message` to stderr. If `severity` exceeds kInfo +// we also flush the stderr stream. +void WriteToStderr(absl::string_view message, absl::LogSeverity severity); + +// Set the TimeZone used for human-friendly times (for example, the log message +// prefix) printed by the logging library. This may only be called once. +void SetTimeZone(absl::TimeZone tz); + +// Returns the TimeZone used for human-friendly times (for example, the log +// message prefix) printed by the logging library Returns nullptr prior to +// initialization. +const absl::TimeZone* TimeZone(); + +// Returns true if stack traces emitted by the logging library should be +// symbolized. This function is async-signal-safe. +bool ShouldSymbolizeLogStackTrace(); + +// Enables or disables symbolization of stack traces emitted by the +// logging library. This function is async-signal-safe. +void EnableSymbolizeLogStackTrace(bool on_off); + +// Returns the maximum number of frames that appear in stack traces +// emitted by the logging library. This function is async-signal-safe. +int MaxFramesInLogStackTrace(); + +// Sets the maximum number of frames that appear in stack traces emitted by +// the logging library. This function is async-signal-safe. +void SetMaxFramesInLogStackTrace(int max_num_frames); + +// Determines whether we exit the program for a LOG(DFATAL) message in +// debug mode. It does this by skipping the call to Fail/FailQuietly. +// This is intended for testing only. +// +// This can have some effects on LOG(FATAL) as well. Failure messages +// are always allocated (rather than sharing a buffer), the crash +// reason is not recorded, the "gwq" status message is not updated, +// and the stack trace is not recorded. The LOG(FATAL) *will* still +// exit the program. Since this function is used only in testing, +// these differences are acceptable. +// +// Additionally, LOG(LEVEL(FATAL)) is indistinguishable from LOG(DFATAL) and +// will not terminate the program if SetExitOnDFatal(false) has been called. +bool ExitOnDFatal(); + +// SetExitOnDFatal() sets the ExitOnDFatal() status +void SetExitOnDFatal(bool on_off); + +// Determines if the logging library should suppress logging of stacktraces in +// the `SIGABRT` handler, typically because we just logged a stacktrace as part +// of `LOG(FATAL)` and are about to send ourselves a `SIGABRT` to end the +// program. +bool SuppressSigabortTrace(); + +// Sets the SuppressSigabortTrace() status and returns the previous state. +bool SetSuppressSigabortTrace(bool on_off); + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_GLOBALS_H_ diff --git a/absl/log/internal/log_format.cc b/absl/log/internal/log_format.cc new file mode 100644 index 00000000..b10a656b --- /dev/null +++ b/absl/log/internal/log_format.cc @@ -0,0 +1,189 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/internal/log_format.h" + +#include <string.h> + +#ifdef _MSC_VER +#include <winsock2.h> // For timeval +#else +#include <sys/time.h> +#endif + +#include <cstddef> +#include <cstdint> +#include <limits> +#include <string> +#include <type_traits> + +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/base/optimization.h" +#include "absl/log/internal/config.h" +#include "absl/log/internal/globals.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "absl/time/civil_time.h" +#include "absl/time/time.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +namespace { + +// The fields before the filename are all fixed-width except for the thread ID, +// which is of bounded width. +size_t FormatBoundedFields(absl::LogSeverity severity, absl::Time timestamp, + log_internal::Tid tid, absl::Span<char>& buf) { + constexpr size_t kBoundedFieldsMaxLen = + sizeof("SMMDD HH:MM:SS.NNNNNN ") + + (1 + std::numeric_limits<log_internal::Tid>::digits10 + 1) - sizeof(""); + if (ABSL_PREDICT_FALSE(buf.size() < kBoundedFieldsMaxLen)) { + // We don't bother trying to truncate these fields if the buffer is too + // short (or almost too short) because it would require doing a lot more + // length checking (slow) and it should never happen. A 15kB buffer should + // be enough for anyone. Instead we mark `buf` full without writing + // anything. + buf.remove_suffix(buf.size()); + return 0; + } + + // We can't call absl::LocalTime(), localtime_r(), or anything else here that + // isn't async-signal-safe. We can only use the time zone if it has already + // been loaded. + const absl::TimeZone* tz = absl::log_internal::TimeZone(); + if (ABSL_PREDICT_FALSE(tz == nullptr)) { + // If a time zone hasn't been set yet because we are logging before the + // logging library has been initialized, we fallback to a simpler, slower + // method. Just report the raw Unix time in seconds. We cram this into the + // normal time format for the benefit of parsers. + auto tv = absl::ToTimeval(timestamp); + int snprintf_result = absl::SNPrintF( + buf.data(), buf.size(), "%c0000 00:00:%02d.%06d %7d ", + absl::LogSeverityName(severity)[0], static_cast<int>(tv.tv_sec), + static_cast<int>(tv.tv_usec), static_cast<int>(tid)); + if (snprintf_result >= 0) { + buf.remove_prefix(snprintf_result); + return static_cast<size_t>(snprintf_result); + } + return 0; + } + + char* p = buf.data(); + *p++ = absl::LogSeverityName(severity)[0]; + const absl::TimeZone::CivilInfo ci = tz->At(timestamp); + absl::numbers_internal::PutTwoDigits(ci.cs.month(), p); + p += 2; + absl::numbers_internal::PutTwoDigits(ci.cs.day(), p); + p += 2; + *p++ = ' '; + absl::numbers_internal::PutTwoDigits(ci.cs.hour(), p); + p += 2; + *p++ = ':'; + absl::numbers_internal::PutTwoDigits(ci.cs.minute(), p); + p += 2; + *p++ = ':'; + absl::numbers_internal::PutTwoDigits(ci.cs.second(), p); + p += 2; + *p++ = '.'; + const int64_t usecs = absl::ToInt64Microseconds(ci.subsecond); + absl::numbers_internal::PutTwoDigits(usecs / 10000, p); + p += 2; + absl::numbers_internal::PutTwoDigits(usecs / 100 % 100, p); + p += 2; + absl::numbers_internal::PutTwoDigits(usecs % 100, p); + p += 2; + *p++ = ' '; + constexpr bool unsigned_tid_t = !std::is_signed<log_internal::Tid>::value; + if ((unsigned_tid_t || tid >= 0) && tid < 10) *p++ = ' '; + if ((unsigned_tid_t || tid > -10) && tid < 100) *p++ = ' '; + if ((unsigned_tid_t || tid > -100) && tid < 1000) *p++ = ' '; + if ((unsigned_tid_t || tid > -1000) && tid < 10000) *p++ = ' '; + if ((unsigned_tid_t || tid > -10000) && tid < 100000) *p++ = ' '; + if ((unsigned_tid_t || tid > -100000) && tid < 1000000) *p++ = ' '; + p = absl::numbers_internal::FastIntToBuffer(tid, p); + *p++ = ' '; + const size_t bytes_formatted = p - buf.data(); + buf.remove_prefix(bytes_formatted); + return bytes_formatted; +} + +// Copies into `dst` as many bytes of `src` as will fit, then advances `dst` +// past the copied bytes and returns the number of bytes written. +size_t AppendTruncated(absl::string_view src, absl::Span<char>& dst) { + if (src.size() > dst.size()) src = src.substr(0, dst.size()); + memcpy(dst.data(), src.data(), src.size()); + dst.remove_prefix(src.size()); + return src.size(); +} + +size_t FormatLineNumber(int line, absl::Span<char>& buf) { + constexpr size_t kLineFieldMaxLen = + sizeof(":] ") + (1 + std::numeric_limits<int>::digits10 + 1) - sizeof(""); + if (ABSL_PREDICT_FALSE(buf.size() < kLineFieldMaxLen)) { + // As above, we don't bother trying to truncate this if the buffer is too + // short and it should never happen. + buf.remove_suffix(buf.size()); + return 0; + } + char* p = buf.data(); + *p++ = ':'; + p = absl::numbers_internal::FastIntToBuffer(line, p); + *p++ = ']'; + *p++ = ' '; + const size_t bytes_formatted = p - buf.data(); + buf.remove_prefix(bytes_formatted); + return bytes_formatted; +} + +} // namespace + +std::string FormatLogMessage(absl::LogSeverity severity, + absl::CivilSecond civil_second, + absl::Duration subsecond, log_internal::Tid tid, + absl::string_view basename, int line, + absl::string_view message) { + return absl::StrFormat( + "%c%02d%02d %02d:%02d:%02d.%06d %7d %s:%d] %s", + absl::LogSeverityName(severity)[0], civil_second.month(), + civil_second.day(), civil_second.hour(), civil_second.minute(), + civil_second.second(), absl::ToInt64Microseconds(subsecond), tid, + basename, line, message); +} + +// This method is fairly hot, and the library always passes a huge `buf`, so we +// save some bounds-checking cycles by not trying to do precise truncation. +// Truncating at a field boundary is probably a better UX anyway. +// +// The prefix is written in three parts, each of which does a single +// bounds-check and truncation: +// 1. severity, timestamp, and thread ID +// 2. filename +// 3. line number and bracket +size_t FormatLogPrefix(absl::LogSeverity severity, absl::Time timestamp, + log_internal::Tid tid, absl::string_view basename, + int line, absl::Span<char>& buf) { + auto prefix_size = FormatBoundedFields(severity, timestamp, tid, buf); + prefix_size += AppendTruncated(basename, buf); + prefix_size += FormatLineNumber(line, buf); + return prefix_size; +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/log_format.h b/absl/log/internal/log_format.h new file mode 100644 index 00000000..a016328f --- /dev/null +++ b/absl/log/internal/log_format.h @@ -0,0 +1,73 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/log_format.h +// ----------------------------------------------------------------------------- +// +// This file declares routines implementing formatting of log message and log +// prefix. + +#ifndef ABSL_LOG_INTERNAL_LOG_FORMAT_H_ +#define ABSL_LOG_INTERNAL_LOG_FORMAT_H_ + +#include <stddef.h> + +#include <string> + +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/internal/config.h" +#include "absl/strings/string_view.h" +#include "absl/time/civil_time.h" +#include "absl/time/time.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// Formats log message based on provided data. +std::string FormatLogMessage(absl::LogSeverity severity, + absl::CivilSecond civil_second, + absl::Duration subsecond, log_internal::Tid tid, + absl::string_view basename, int line, + absl::string_view message); + +// Formats various entry metadata into a text string meant for use as a +// prefix on a log message string. Writes into `buf`, advances `buf` to point +// at the remainder of the buffer (i.e. past any written bytes), and returns the +// number of bytes written. +// +// In addition to calling `buf->remove_prefix()` (or the equivalent), this +// function may also do `buf->remove_suffix(buf->size())` in cases where no more +// bytes (i.e. no message data) should be written into the buffer. For example, +// if the prefix ought to be: +// I0926 09:00:00.000000 1234567 foo.cc:123] +// `buf` is too small, the function might fill the whole buffer: +// I0926 09:00:00.000000 1234 +// (note the apparrently incorrect thread ID), or it might write less: +// I0926 09:00:00.000000 +// In this case, it might also empty `buf` prior to returning to prevent +// message data from being written into the space where a reader would expect to +// see a thread ID. +size_t FormatLogPrefix(absl::LogSeverity severity, absl::Time timestamp, + log_internal::Tid tid, absl::string_view basename, + int line, absl::Span<char>& buf); + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_LOG_FORMAT_H_ diff --git a/absl/log/internal/log_message.cc b/absl/log/internal/log_message.cc new file mode 100644 index 00000000..9ef0c29e --- /dev/null +++ b/absl/log/internal/log_message.cc @@ -0,0 +1,508 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/internal/log_message.h" + +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#ifndef _WIN32 +#include <unistd.h> +#endif + +#include <algorithm> +#include <array> +#include <atomic> +#include <memory> +#include <ostream> +#include <string> +#include <tuple> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/base/internal/strerror.h" +#include "absl/base/internal/sysinfo.h" +#include "absl/base/log_severity.h" +#include "absl/container/inlined_vector.h" +#include "absl/debugging/internal/examine_stack.h" +#include "absl/log/globals.h" +#include "absl/log/internal/config.h" +#include "absl/log/internal/globals.h" +#include "absl/log/internal/log_format.h" +#include "absl/log/internal/log_sink_set.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" +#include "absl/log/log_sink_registry.h" +#include "absl/memory/memory.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "absl/types/span.h" + +extern "C" ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL( + AbslInternalOnFatalLogMessage)(const absl::LogEntry&) { + // Default - Do nothing +} + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +namespace { +// Copies into `dst` as many bytes of `src` as will fit, then truncates the +// copied bytes from the front of `dst` and returns the number of bytes written. +size_t AppendTruncated(absl::string_view src, absl::Span<char>* dst) { + if (src.size() > dst->size()) src = src.substr(0, dst->size()); + memcpy(dst->data(), src.data(), src.size()); + dst->remove_prefix(src.size()); + return src.size(); +} + +absl::string_view Basename(absl::string_view filepath) { +#ifdef _WIN32 + size_t path = filepath.find_last_of("/\\"); +#else + size_t path = filepath.find_last_of('/'); +#endif + if (path != filepath.npos) filepath.remove_prefix(path + 1); + return filepath; +} + +void WriteToString(const char* data, void* str) { + reinterpret_cast<std::string*>(str)->append(data); +} +void WriteToStream(const char* data, void* os) { + auto* cast_os = static_cast<std::ostream*>(os); + *cast_os << data; +} +} // namespace + +// A write-only `std::streambuf` that writes into an `absl::Span<char>`. +// +// This class is responsible for writing a metadata prefix just before the first +// data are streamed in. The metadata are subject to change (cf. +// `LogMessage::AtLocation`) until then, so we wait as long as possible. +// +// This class is also responsible for reserving space for a trailing newline +// so that one can be added later by `Finalize` no matter how many data are +// streamed in. +class LogEntryStreambuf final : public std::streambuf { + public: + explicit LogEntryStreambuf(absl::Span<char> buf, const absl::LogEntry& entry) + : buf_(buf), entry_(entry), prefix_len_(0), finalized_(false) { + // To detect when data are first written, we leave the put area null, + // override `overflow`, and check ourselves in `xsputn`. + } + + LogEntryStreambuf(LogEntryStreambuf&&) = delete; + LogEntryStreambuf& operator=(LogEntryStreambuf&&) = delete; + + absl::Span<const char> Finalize() { + assert(!finalized_); + // If no data were ever streamed in, this is where we must write the prefix. + if (pbase() == nullptr) Initialize(); + // Here we reclaim the two bytes we reserved. + size_t idx = pptr() - pbase(); + setp(buf_.data(), buf_.data() + buf_.size()); + pbump(idx); + sputc('\n'); + sputc('\0'); + finalized_ = true; + return absl::Span<const char>(pbase(), pptr() - pbase()); + } + size_t prefix_len() const { return prefix_len_; } + + protected: + std::streamsize xsputn(const char* s, std::streamsize n) override { + if (pbase() == nullptr) Initialize(); + return Append(absl::string_view(s, n)); + } + + int overflow(int ch = EOF) override { + if (pbase() == nullptr) Initialize(); + if (ch == EOF) return 0; + if (pptr() == epptr()) return EOF; + *pptr() = static_cast<char>(ch); + pbump(1); + return 1; + } + + private: + void Initialize() { + // Here we reserve two bytes in our buffer to guarantee `Finalize` space to + // add a trailing "\n\0". + assert(buf_.size() >= 2); + setp(buf_.data(), buf_.data() + buf_.size() - 2); + if (entry_.prefix()) { + absl::Span<char> remaining = buf_; + prefix_len_ = log_internal::FormatLogPrefix( + entry_.log_severity(), entry_.timestamp(), entry_.tid(), + entry_.source_basename(), entry_.source_line(), remaining); + pbump(prefix_len_); + } + } + + size_t Append(absl::string_view data) { + absl::Span<char> remaining(pptr(), epptr() - pptr()); + const size_t written = AppendTruncated(data, &remaining); + pbump(written); + return written; + } + + const absl::Span<char> buf_; + const absl::LogEntry& entry_; + size_t prefix_len_; + bool finalized_; +}; + +struct LogMessage::LogMessageData final { + LogMessageData(const char* file, int line, absl::LogSeverity severity, + absl::Time timestamp); + LogMessageData(const LogMessageData&) = delete; + LogMessageData& operator=(const LogMessageData&) = delete; + + // `LogEntry` sent to `LogSink`s; contains metadata. + absl::LogEntry entry; + + // true => this was first fatal msg + bool first_fatal; + // true => all failures should be quiet + bool fail_quietly; + // true => PLOG was requested + bool is_perror; + + // Extra `LogSink`s to log to, in addition to `global_sinks`. + absl::InlinedVector<absl::LogSink*, 16> extra_sinks; + // If true, log to `extra_sinks` but not to `global_sinks` or hardcoded + // non-sink targets (e.g. stderr, log files). + bool extra_sinks_only; + + // A formatted string message is built in `string_buf`. + std::array<char, kLogMessageBufferSize> string_buf; + + // A `std::streambuf` that stores into `string_buf`. + LogEntryStreambuf streambuf_; +}; + +LogMessage::LogMessageData::LogMessageData(const char* file, int line, + absl::LogSeverity severity, + absl::Time timestamp) + : extra_sinks_only(false), + streambuf_(absl::MakeSpan(string_buf), entry) { + entry.full_filename_ = file; + entry.base_filename_ = Basename(file); + entry.line_ = line; + entry.prefix_ = absl::ShouldPrependLogPrefix(); + entry.severity_ = absl::NormalizeLogSeverity(severity); + entry.verbose_level_ = absl::LogEntry::kNoVerbosityLevel; + entry.timestamp_ = timestamp; + entry.tid_ = absl::base_internal::GetCachedTID(); +} + +LogMessage::LogMessage(const char* file, int line, absl::LogSeverity severity) + : data_( + absl::make_unique<LogMessageData>(file, line, severity, absl::Now())) + , + stream_(&data_->streambuf_) +{ + data_->first_fatal = false; + data_->is_perror = false; + data_->fail_quietly = false; + + // Legacy defaults for LOG's ostream: + stream_.setf(std::ios_base::showbase | std::ios_base::boolalpha); + // `fill('0')` is omitted here because its effects are very different without + // structured logging. Resolution is tracked in b/111310488. + + // This logs a backtrace even if the location is subsequently changed using + // AtLocation. This quirk, and the behavior when AtLocation is called twice, + // are fixable but probably not worth fixing. + LogBacktraceIfNeeded(); +} + +LogMessage::~LogMessage() { +#ifdef ABSL_MIN_LOG_LEVEL + if (data_->entry.log_severity() < + static_cast<absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) && + data_->entry.log_severity() < absl::LogSeverity::kFatal) { + return; + } +#endif + Flush(); +} + +LogMessage& LogMessage::AtLocation(absl::string_view file, int line) { + data_->entry.full_filename_ = file; + data_->entry.base_filename_ = Basename(file); + data_->entry.line_ = line; + LogBacktraceIfNeeded(); + return *this; +} + +LogMessage& LogMessage::NoPrefix() { + data_->entry.prefix_ = false; + return *this; +} + +LogMessage& LogMessage::WithVerbosity(int verbose_level) { + if (verbose_level == absl::LogEntry::kNoVerbosityLevel) { + data_->entry.verbose_level_ = absl::LogEntry::kNoVerbosityLevel; + } else { + data_->entry.verbose_level_ = std::max(0, verbose_level); + } + return *this; +} + +LogMessage& LogMessage::WithTimestamp(absl::Time timestamp) { + data_->entry.timestamp_ = timestamp; + return *this; +} + +LogMessage& LogMessage::WithThreadID(absl::LogEntry::tid_t tid) { + data_->entry.tid_ = tid; + return *this; +} + +LogMessage& LogMessage::WithMetadataFrom(const absl::LogEntry& entry) { + data_->entry.full_filename_ = entry.full_filename_; + data_->entry.base_filename_ = entry.base_filename_; + data_->entry.line_ = entry.line_; + data_->entry.prefix_ = entry.prefix_; + data_->entry.severity_ = entry.severity_; + data_->entry.verbose_level_ = entry.verbose_level_; + data_->entry.timestamp_ = entry.timestamp_; + data_->entry.tid_ = entry.tid_; + return *this; +} + +LogMessage& LogMessage::WithPerror() { + data_->is_perror = true; + return *this; +} + +LogMessage& LogMessage::ToSinkAlso(absl::LogSink* sink) { + ABSL_INTERNAL_CHECK(sink, "null LogSink*"); + data_->extra_sinks.push_back(sink); + return *this; +} + +LogMessage& LogMessage::ToSinkOnly(absl::LogSink* sink) { + ABSL_INTERNAL_CHECK(sink, "null LogSink*"); + data_->extra_sinks.clear(); + data_->extra_sinks.push_back(sink); + data_->extra_sinks_only = true; + return *this; +} + +#ifdef __ELF__ +extern "C" void __gcov_dump() ABSL_ATTRIBUTE_WEAK; +extern "C" void __gcov_flush() ABSL_ATTRIBUTE_WEAK; +#endif + +void LogMessage::FailWithoutStackTrace() { + // Now suppress repeated trace logging: + log_internal::SetSuppressSigabortTrace(true); +#if defined _DEBUG && defined COMPILER_MSVC + // When debugging on windows, avoid the obnoxious dialog. + __debugbreak(); +#endif + +#ifdef __ELF__ + // For b/8737634, flush coverage if we are in coverage mode. + if (&__gcov_dump != nullptr) { + __gcov_dump(); + } else if (&__gcov_flush != nullptr) { + __gcov_flush(); + } +#endif + + abort(); +} + +void LogMessage::FailQuietly() { + // _exit. Calling abort() would trigger all sorts of death signal handlers + // and a detailed stack trace. Calling exit() would trigger the onexit + // handlers, including the heap-leak checker, which is guaranteed to fail in + // this case: we probably just new'ed the std::string that we logged. + // Anyway, if you're calling Fail or FailQuietly, you're trying to bail out + // of the program quickly, and it doesn't make much sense for FailQuietly to + // offer different guarantees about exit behavior than Fail does. (And as a + // consequence for QCHECK and CHECK to offer different exit behaviors) + _exit(1); +} + +template LogMessage& LogMessage::operator<<(const char& v); +template LogMessage& LogMessage::operator<<(const signed char& v); +template LogMessage& LogMessage::operator<<(const unsigned char& v); +template LogMessage& LogMessage::operator<<(const short& v); // NOLINT +template LogMessage& LogMessage::operator<<(const unsigned short& v); // NOLINT +template LogMessage& LogMessage::operator<<(const int& v); +template LogMessage& LogMessage::operator<<(const unsigned int& v); +template LogMessage& LogMessage::operator<<(const long& v); // NOLINT +template LogMessage& LogMessage::operator<<(const unsigned long& v); // NOLINT +template LogMessage& LogMessage::operator<<(const long long& v); // NOLINT +template LogMessage& LogMessage::operator<<( + const unsigned long long& v); // NOLINT +template LogMessage& LogMessage::operator<<(void* const& v); +template LogMessage& LogMessage::operator<<(const void* const& v); +template LogMessage& LogMessage::operator<<(const float& v); +template LogMessage& LogMessage::operator<<(const double& v); +template LogMessage& LogMessage::operator<<(const bool& v); +template LogMessage& LogMessage::operator<<(const std::string& v); +template LogMessage& LogMessage::operator<<(const absl::string_view& v); + +void LogMessage::Flush() { + if (data_->entry.log_severity() < absl::MinLogLevel()) + return; + + if (data_->is_perror) { + InternalStream() << ": " << absl::base_internal::StrError(errno_saver_()) + << " [" << errno_saver_() << "]"; + } + + // Have we already seen a fatal message? + ABSL_CONST_INIT static std::atomic_flag seen_fatal = ATOMIC_FLAG_INIT; + if (data_->entry.log_severity() == absl::LogSeverity::kFatal && + absl::log_internal::ExitOnDFatal()) { + // Exactly one LOG(FATAL) message is responsible for aborting the process, + // even if multiple threads LOG(FATAL) concurrently. + data_->first_fatal = !seen_fatal.test_and_set(std::memory_order_relaxed); + } + + data_->entry.text_message_with_prefix_and_newline_and_nul_ = + data_->streambuf_.Finalize(); + data_->entry.prefix_len_ = data_->streambuf_.prefix_len(); + SendToLog(); +} + +void LogMessage::SetFailQuietly() { data_->fail_quietly = true; } + +bool LogMessage::IsFatal() const { + return data_->entry.log_severity() == absl::LogSeverity::kFatal && + absl::log_internal::ExitOnDFatal(); +} + +void LogMessage::PrepareToDie() { + // If we log a FATAL message, flush all the log destinations, then toss + // a signal for others to catch. We leave the logs in a state that + // someone else can use them (as long as they flush afterwards) + if (data_->first_fatal) { + // Notify observers about the upcoming fatal error. + ABSL_INTERNAL_C_SYMBOL(AbslInternalOnFatalLogMessage)(data_->entry); + } + + if (!data_->fail_quietly) { + // Log the message first before we start collecting stack trace. + log_internal::LogToSinks(data_->entry, absl::MakeSpan(data_->extra_sinks), + data_->extra_sinks_only); + + // `DumpStackTrace` generates an empty string under MSVC. + // Adding the constant prefix here simplifies testing. + data_->entry.stacktrace_ = "*** Check failure stack trace: ***\n"; + debugging_internal::DumpStackTrace( + 0, log_internal::MaxFramesInLogStackTrace(), + log_internal::ShouldSymbolizeLogStackTrace(), WriteToString, + &data_->entry.stacktrace_); + } +} + +void LogMessage::Die() { + absl::FlushLogSinks(); + + if (data_->fail_quietly) { + FailQuietly(); + } else { + FailWithoutStackTrace(); + } +} + +void LogMessage::SendToLog() { + if (IsFatal()) PrepareToDie(); + // Also log to all registered sinks, even if OnlyLogToStderr() is set. + log_internal::LogToSinks(data_->entry, absl::MakeSpan(data_->extra_sinks), + data_->extra_sinks_only); + if (IsFatal()) Die(); +} + +void LogMessage::LogBacktraceIfNeeded() { + if (!absl::log_internal::IsInitialized()) return; + + if (!absl::log_internal::ShouldLogBacktraceAt(data_->entry.source_basename(), + data_->entry.source_line())) + return; + stream_ << " (stacktrace:\n"; + debugging_internal::DumpStackTrace( + 1, log_internal::MaxFramesInLogStackTrace(), + log_internal::ShouldSymbolizeLogStackTrace(), WriteToStream, &stream_); + stream_ << ") "; +} + +LogMessageFatal::LogMessageFatal(const char* file, int line) + : LogMessage(file, line, absl::LogSeverity::kFatal) {} + +LogMessageFatal::LogMessageFatal(const char* file, int line, + absl::string_view failure_msg) + : LogMessage(file, line, absl::LogSeverity::kFatal) { + *this << "Check failed: " << failure_msg << " "; +} + +// ABSL_ATTRIBUTE_NORETURN doesn't seem to work on destructors with msvc, so +// disable msvc's warning about the d'tor never returning. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4722) +#endif +LogMessageFatal::~LogMessageFatal() { + Flush(); + FailWithoutStackTrace(); +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +LogMessageQuietlyFatal::LogMessageQuietlyFatal(const char* file, int line) + : LogMessage(file, line, absl::LogSeverity::kFatal) { + SetFailQuietly(); +} + +LogMessageQuietlyFatal::LogMessageQuietlyFatal(const char* file, int line, + absl::string_view failure_msg) + : LogMessage(file, line, absl::LogSeverity::kFatal) { + SetFailQuietly(); + *this << "Check failed: " << failure_msg << " "; +} + +// ABSL_ATTRIBUTE_NORETURN doesn't seem to work on destructors with msvc, so +// disable msvc's warning about the d'tor never returning. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4722) +#endif +LogMessageQuietlyFatal::~LogMessageQuietlyFatal() { + Flush(); + FailQuietly(); +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +} // namespace log_internal + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/log_message.h b/absl/log/internal/log_message.h new file mode 100644 index 00000000..37a267c0 --- /dev/null +++ b/absl/log/internal/log_message.h @@ -0,0 +1,287 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/log_message.h +// ----------------------------------------------------------------------------- +// +// This file declares `class absl::log_internal::LogMessage`. This class more or +// less represents a particular log message. LOG/CHECK macros create a +// temporary instance of `LogMessage` and then stream values to it. At the end +// of the LOG/CHECK statement, LogMessage instance goes out of scope and +// `~LogMessage` directs the message to the registered log sinks. +// Heap-allocation of `LogMessage` is unsupported. Construction outside of a +// `LOG` macro is unsupported. + +#ifndef ABSL_LOG_INTERNAL_LOG_MESSAGE_H_ +#define ABSL_LOG_INTERNAL_LOG_MESSAGE_H_ + +#include <ios> +#include <memory> +#include <ostream> +#include <streambuf> +#include <string> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/internal/errno_saver.h" +#include "absl/base/log_severity.h" +#include "absl/log/internal/config.h" +#include "absl/log/internal/nullguard.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +constexpr int kLogMessageBufferSize = 15000; + +class LogMessage { + public: + // Used for `LOG`. + LogMessage(const char* file, int line, + absl::LogSeverity severity) ABSL_ATTRIBUTE_COLD; + LogMessage(const LogMessage&) = delete; + LogMessage& operator=(const LogMessage&) = delete; + ~LogMessage() ABSL_ATTRIBUTE_COLD; + + // Overrides the location inferred from the callsite. The string pointed to + // by `file` must be valid until the end of the statement. + LogMessage& AtLocation(absl::string_view file, int line); + // Omits the prefix from this line. The prefix includes metadata about the + // logged data such as source code location and timestamp. + LogMessage& NoPrefix(); + // Sets the verbosity field of the logged message as if it was logged by + // `VLOG(verbose_level)`. Unlike `VLOG`, this method does not affect + // evaluation of the statement when the specified `verbose_level` has been + // disabled. The only effect is on `absl::LogSink` implementations which + // make use of the `absl::LogSink::verbosity()` value. The value + // `absl::LogEntry::kNoVerbosityLevel` can be specified to mark the message + // not verbose. + LogMessage& WithVerbosity(int verbose_level); + // Uses the specified timestamp instead of one collected in the constructor. + LogMessage& WithTimestamp(absl::Time timestamp); + // Uses the specified thread ID instead of one collected in the constructor. + LogMessage& WithThreadID(absl::LogEntry::tid_t tid); + // Copies all metadata (but no data) from the specified `absl::LogEntry`. + LogMessage& WithMetadataFrom(const absl::LogEntry& entry); + // Appends to the logged message a colon, a space, a textual description of + // the current value of `errno` (as by strerror(3)), and the numerical value + // of `errno`. + LogMessage& WithPerror(); + // Sends this message to `*sink` in addition to whatever other sinks it would + // otherwise have been sent to. `sink` must not be null. + LogMessage& ToSinkAlso(absl::LogSink* sink); + // Sends this message to `*sink` and no others. `sink` must not be null. + LogMessage& ToSinkOnly(absl::LogSink* sink); + + // Don't call this method from outside this library. + LogMessage& InternalStream() { return *this; } + + // By-value overloads for small, common types let us overlook common failures + // to define globals and static data members (i.e. in a .cc file). + // clang-format off + // The CUDA toolchain cannot handle these <<<'s: + LogMessage& operator<<(char v) { return operator<< <char>(v); } + LogMessage& operator<<(signed char v) { return operator<< <signed char>(v); } + LogMessage& operator<<(unsigned char v) { + return operator<< <unsigned char>(v); + } + LogMessage& operator<<(signed short v) { // NOLINT + return operator<< <signed short>(v); // NOLINT + } + LogMessage& operator<<(signed int v) { return operator<< <signed int>(v); } + LogMessage& operator<<(signed long v) { // NOLINT + return operator<< <signed long>(v); // NOLINT + } + LogMessage& operator<<(signed long long v) { // NOLINT + return operator<< <signed long long>(v); // NOLINT + } + LogMessage& operator<<(unsigned short v) { // NOLINT + return operator<< <unsigned short>(v); // NOLINT + } + LogMessage& operator<<(unsigned int v) { + return operator<< <unsigned int>(v); + } + LogMessage& operator<<(unsigned long v) { // NOLINT + return operator<< <unsigned long>(v); // NOLINT + } + LogMessage& operator<<(unsigned long long v) { // NOLINT + return operator<< <unsigned long long>(v); // NOLINT + } + LogMessage& operator<<(void* v) { return operator<< <void*>(v); } + LogMessage& operator<<(const void* v) { return operator<< <const void*>(v); } + LogMessage& operator<<(float v) { return operator<< <float>(v); } + LogMessage& operator<<(double v) { return operator<< <double>(v); } + LogMessage& operator<<(bool v) { return operator<< <bool>(v); } + // clang-format on + + // Handle stream manipulators e.g. std::endl. + LogMessage& operator<<(std::ostream& (*m)(std::ostream& os)); + LogMessage& operator<<(std::ios_base& (*m)(std::ios_base& os)); + + // Literal strings. This allows us to record C string literals as literals in + // the logging.proto.Value. + // + // Allow this overload to be inlined to prevent generating instantiations of + // this template for every value of `SIZE` encountered in each source code + // file. That significantly increases linker input sizes. Inlining is cheap + // because the argument to this overload is almost always a string literal so + // the call to `strlen` can be replaced at compile time. The overload for + // `char[]` below should not be inlined. The compiler typically does not have + // the string at compile time and cannot replace the call to `strlen` so + // inlining it increases the binary size. See the discussion on + // cl/107527369. + template <int SIZE> + LogMessage& operator<<(const char (&buf)[SIZE]); + + // This prevents non-const `char[]` arrays from looking like literals. + template <int SIZE> + LogMessage& operator<<(char (&buf)[SIZE]) ABSL_ATTRIBUTE_NOINLINE; + + // Default: uses `ostream` logging to convert `v` to a string. + template <typename T> + LogMessage& operator<<(const T& v) ABSL_ATTRIBUTE_NOINLINE; + + // Note: We explicitly do not support `operator<<` for non-const references + // because it breaks logging of non-integer bitfield types (i.e., enums). + + protected: + // Call `abort()` or similar to perform `LOG(FATAL)` crash. It is assumed + // that the caller has already generated and written the trace as appropriate. + ABSL_ATTRIBUTE_NORETURN static void FailWithoutStackTrace(); + + // Similar to `FailWithoutStackTrace()`, but without `abort()`. Terminates + // the process with an error exit code. + ABSL_ATTRIBUTE_NORETURN static void FailQuietly(); + + // Dispatches the completed `absl::LogEntry` to applicable `absl::LogSink`s. + // This might as well be inlined into `~LogMessage` except that + // `~LogMessageFatal` needs to call it early. + void Flush(); + + // After this is called, failures are done as quiet as possible for this log + // message. + void SetFailQuietly(); + + private: + struct LogMessageData; // Opaque type containing message state + + // Returns `true` if the message is fatal or enabled debug-fatal. + bool IsFatal() const; + + // Records some tombstone-type data in anticipation of `Die`. + void PrepareToDie(); + void Die(); + + void SendToLog(); + + // Checks `FLAGS_log_backtrace_at` and appends a backtrace if appropriate. + void LogBacktraceIfNeeded(); + + // This should be the first data member so that its initializer captures errno + // before any other initializers alter it (e.g. with calls to new) and so that + // no other destructors run afterward an alter it (e.g. with calls to delete). + absl::base_internal::ErrnoSaver errno_saver_; + + // We keep the data in a separate struct so that each instance of `LogMessage` + // uses less stack space. + std::unique_ptr<LogMessageData> data_; + + std::ostream stream_; +}; + +// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` +template <typename T> +LogMessage& LogMessage::operator<<(const T& v) { + stream_ << log_internal::NullGuard<T>().Guard(v); + return *this; +} +inline LogMessage& LogMessage::operator<<( + std::ostream& (*m)(std::ostream& os)) { + stream_ << m; + return *this; +} +inline LogMessage& LogMessage::operator<<( + std::ios_base& (*m)(std::ios_base& os)) { + stream_ << m; + return *this; +} +template <int SIZE> +LogMessage& LogMessage::operator<<(const char (&buf)[SIZE]) { + stream_ << buf; + return *this; +} +// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` +template <int SIZE> +LogMessage& LogMessage::operator<<(char (&buf)[SIZE]) { + stream_ << buf; + return *this; +} +// We instantiate these specializations in the library's TU to save space in +// other TUs. Since the template is marked `ABSL_ATTRIBUTE_NOINLINE` we will be +// emitting a function call either way. +extern template LogMessage& LogMessage::operator<<(const char& v); +extern template LogMessage& LogMessage::operator<<(const signed char& v); +extern template LogMessage& LogMessage::operator<<(const unsigned char& v); +extern template LogMessage& LogMessage::operator<<(const short& v); // NOLINT +extern template LogMessage& LogMessage::operator<<( + const unsigned short& v); // NOLINT +extern template LogMessage& LogMessage::operator<<(const int& v); +extern template LogMessage& LogMessage::operator<<( + const unsigned int& v); // NOLINT +extern template LogMessage& LogMessage::operator<<(const long& v); // NOLINT +extern template LogMessage& LogMessage::operator<<( + const unsigned long& v); // NOLINT +extern template LogMessage& LogMessage::operator<<( + const long long& v); // NOLINT +extern template LogMessage& LogMessage::operator<<( + const unsigned long long& v); // NOLINT +extern template LogMessage& LogMessage::operator<<(void* const& v); +extern template LogMessage& LogMessage::operator<<(const void* const& v); +extern template LogMessage& LogMessage::operator<<(const float& v); +extern template LogMessage& LogMessage::operator<<(const double& v); +extern template LogMessage& LogMessage::operator<<(const bool& v); +extern template LogMessage& LogMessage::operator<<(const std::string& v); +extern template LogMessage& LogMessage::operator<<(const absl::string_view& v); + +// `LogMessageFatal` ensures the process will exit in failure after logging this +// message. +class LogMessageFatal final : public LogMessage { + public: + LogMessageFatal(const char* file, int line) ABSL_ATTRIBUTE_COLD; + LogMessageFatal(const char* file, int line, + absl::string_view failure_msg) ABSL_ATTRIBUTE_COLD; + ABSL_ATTRIBUTE_NORETURN ~LogMessageFatal(); +}; + +class LogMessageQuietlyFatal final : public LogMessage { + public: + LogMessageQuietlyFatal(const char* file, int line) ABSL_ATTRIBUTE_COLD; + LogMessageQuietlyFatal(const char* file, int line, + absl::string_view failure_msg) ABSL_ATTRIBUTE_COLD; + ABSL_ATTRIBUTE_NORETURN ~LogMessageQuietlyFatal(); +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +extern "C" ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL( + AbslInternalOnFatalLogMessage)(const absl::LogEntry&); + +#endif // ABSL_LOG_INTERNAL_LOG_MESSAGE_H_ diff --git a/absl/log/internal/log_sink_set.cc b/absl/log/internal/log_sink_set.cc new file mode 100644 index 00000000..4fe301c6 --- /dev/null +++ b/absl/log/internal/log_sink_set.cc @@ -0,0 +1,295 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/internal/log_sink_set.h" + +#ifndef ABSL_HAVE_THREAD_LOCAL +#include <pthread.h> +#endif + +#ifdef __ANDROID__ +#include <android/log.h> +#endif + +#ifdef _WIN32 +#include <windows.h> +#endif + +#include <algorithm> +#include <vector> + +#include "absl/base/attributes.h" +#include "absl/base/call_once.h" +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/base/log_severity.h" +#include "absl/base/thread_annotations.h" +#include "absl/cleanup/cleanup.h" +#include "absl/log/globals.h" +#include "absl/log/internal/config.h" +#include "absl/log/internal/globals.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +namespace { + +// Returns a mutable reference to a thread-local variable that should be true if +// a globally-registered `LogSink`'s `Send()` is currently being invoked on this +// thread. +bool& ThreadIsLoggingStatus() { +#ifdef ABSL_HAVE_THREAD_LOCAL + ABSL_CONST_INIT thread_local bool thread_is_logging = false; + return thread_is_logging; +#else + ABSL_CONST_INIT static pthread_key_t thread_is_logging_key; + static const bool unused = [] { + if (pthread_key_create(&thread_is_logging_key, [](void* data) { + delete reinterpret_cast<bool*>(data); + })) { + perror("pthread_key_create failed!"); + abort(); + } + return true; + }(); + bool* thread_is_logging_ptr = + reinterpret_cast<bool*>(pthread_getspecific(thread_is_logging_key)); + + if (ABSL_PREDICT_FALSE(!thread_is_logging_ptr)) { + thread_is_logging_ptr = new bool{false}; + if (pthread_setspecific(thread_is_logging_key, thread_is_logging_ptr)) { + perror("pthread_setspecific failed"); + abort(); + } + } + return *thread_is_logging_ptr; +#endif +} + +class StderrLogSink final : public LogSink { + public: + ~StderrLogSink() override = default; + + void Send(const absl::LogEntry& entry) override { + if (entry.log_severity() < absl::StderrThreshold() && + absl::log_internal::IsInitialized()) { + return; + } + + ABSL_CONST_INIT static absl::once_flag warn_if_not_initialized; + absl::call_once(warn_if_not_initialized, []() { + if (absl::log_internal::IsInitialized()) return; + const char w[] = + "WARNING: All log messages before absl::InitializeLog() is called" + " are written to STDERR\n"; + absl::log_internal::WriteToStderr(w, absl::LogSeverity::kWarning); + }); + + if (!entry.stacktrace().empty()) { + absl::log_internal::WriteToStderr(entry.stacktrace(), + entry.log_severity()); + } else { + // TODO(b/226937039): do this outside else condition once we avoid + // ReprintFatalMessage + absl::log_internal::WriteToStderr( + entry.text_message_with_prefix_and_newline(), entry.log_severity()); + } + } +}; + +#if defined(__ANDROID__) +class AndroidLogSink final : public LogSink { + public: + ~AndroidLogSink() override = default; + + void Send(const absl::LogEntry& entry) override { + const int level = AndroidLogLevel(entry); + // TODO(b/37587197): make the tag ("native") configurable. + __android_log_write(level, "native", + entry.text_message_with_prefix_and_newline_c_str()); + if (entry.log_severity() == absl::LogSeverity::kFatal) + __android_log_write(ANDROID_LOG_FATAL, "native", "terminating.\n"); + } + + private: + static int AndroidLogLevel(const absl::LogEntry& entry) { + switch (entry.log_severity()) { + case absl::LogSeverity::kFatal: + return ANDROID_LOG_FATAL; + case absl::LogSeverity::kError: + return ANDROID_LOG_ERROR; + case absl::LogSeverity::kWarning: + return ANDROID_LOG_WARN; + default: + if (entry.verbosity() >= 2) return ANDROID_LOG_VERBOSE; + if (entry.verbosity() == 1) return ANDROID_LOG_DEBUG; + return ANDROID_LOG_INFO; + } + } +}; +#endif // !defined(__ANDROID__) + +#if defined(_WIN32) +class WindowsDebuggerLogSink final : public LogSink { + public: + ~WindowsDebuggerLogSink() override = default; + + void Send(const absl::LogEntry& entry) override { + if (entry.log_severity() < absl::StderrThreshold() && + absl::log_internal::IsInitialized()) { + return; + } + ::OutputDebugStringA(entry.text_message_with_prefix_and_newline_c_str()); + } +}; +#endif // !defined(_WIN32) + +class GlobalLogSinkSet final { + public: + GlobalLogSinkSet() { +#if defined(__myriad2__) || defined(__Fuchsia__) + // myriad2 and Fuchsia do not log to stderr by default. +#else + static StderrLogSink* stderr_log_sink = new StderrLogSink; + AddLogSink(stderr_log_sink); +#endif +#ifdef __ANDROID__ + static AndroidLogSink* android_log_sink = new AndroidLogSink; + AddLogSink(android_log_sink); +#endif +#if defined(_WIN32) + static WindowsDebuggerLogSink* debugger_log_sink = + new WindowsDebuggerLogSink; + AddLogSink(debugger_log_sink); +#endif // !defined(_WIN32) + } + + void LogToSinks(const absl::LogEntry& entry, + absl::Span<absl::LogSink*> extra_sinks, bool extra_sinks_only) + ABSL_LOCKS_EXCLUDED(guard_) { + SendToSinks(entry, extra_sinks); + + if (!extra_sinks_only) { + if (ThreadIsLoggingToLogSink()) { + absl::log_internal::WriteToStderr( + entry.text_message_with_prefix_and_newline(), entry.log_severity()); + } else { + absl::ReaderMutexLock global_sinks_lock(&guard_); + ThreadIsLoggingStatus() = true; + // Ensure the "thread is logging" status is reverted upon leaving the + // scope even in case of exceptions. + auto status_cleanup = + absl::MakeCleanup([] { ThreadIsLoggingStatus() = false; }); + SendToSinks(entry, absl::MakeSpan(sinks_)); + } + } + } + + void AddLogSink(absl::LogSink* sink) ABSL_LOCKS_EXCLUDED(guard_) { + { + absl::WriterMutexLock global_sinks_lock(&guard_); + auto pos = std::find(sinks_.begin(), sinks_.end(), sink); + if (pos == sinks_.end()) { + sinks_.push_back(sink); + return; + } + } + ABSL_INTERNAL_LOG(FATAL, "Duplicate log sinks are not supported"); + } + + void RemoveLogSink(absl::LogSink* sink) ABSL_LOCKS_EXCLUDED(guard_) { + { + absl::WriterMutexLock global_sinks_lock(&guard_); + auto pos = std::find(sinks_.begin(), sinks_.end(), sink); + if (pos != sinks_.end()) { + sinks_.erase(pos); + return; + } + } + ABSL_INTERNAL_LOG(FATAL, "Mismatched log sink being removed"); + } + + void FlushLogSinks() ABSL_LOCKS_EXCLUDED(guard_) { + if (ThreadIsLoggingToLogSink()) { + // The thread_local condition demonstrates that we're already holding the + // lock in order to iterate over `sinks_` for dispatch. The thread-safety + // annotations don't know this, so we use `ABSL_NO_THREAD_SAFETY_ANALYSIS` + guard_.AssertReaderHeld(); + FlushLogSinksLocked(); + } else { + absl::ReaderMutexLock global_sinks_lock(&guard_); + // In case if LogSink::Flush overload decides to log + ThreadIsLoggingStatus() = true; + // Ensure the "thread is logging" status is reverted upon leaving the + // scope even in case of exceptions. + auto status_cleanup = + absl::MakeCleanup([] { ThreadIsLoggingStatus() = false; }); + FlushLogSinksLocked(); + } + } + + private: + void FlushLogSinksLocked() ABSL_SHARED_LOCKS_REQUIRED(guard_) { + for (absl::LogSink* sink : sinks_) { + sink->Flush(); + } + } + + // Helper routine for LogToSinks. + static void SendToSinks(const absl::LogEntry& entry, + absl::Span<absl::LogSink*> sinks) { + for (absl::LogSink* sink : sinks) { + sink->Send(entry); + } + } + + using LogSinksSet = std::vector<absl::LogSink*>; + absl::Mutex guard_; + LogSinksSet sinks_ ABSL_GUARDED_BY(guard_); +}; + +// Returns reference to the global LogSinks set. +GlobalLogSinkSet& GlobalSinks() { + static GlobalLogSinkSet* global_sinks = new GlobalLogSinkSet; + return *global_sinks; +} + +} // namespace + +bool ThreadIsLoggingToLogSink() { return ThreadIsLoggingStatus(); } + +void LogToSinks(const absl::LogEntry& entry, + absl::Span<absl::LogSink*> extra_sinks, bool extra_sinks_only) { + log_internal::GlobalSinks().LogToSinks(entry, extra_sinks, extra_sinks_only); +} + +void AddLogSink(absl::LogSink* sink) { + log_internal::GlobalSinks().AddLogSink(sink); +} + +void RemoveLogSink(absl::LogSink* sink) { + log_internal::GlobalSinks().RemoveLogSink(sink); +} + +void FlushLogSinks() { log_internal::GlobalSinks().FlushLogSinks(); } + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/log_sink_set.h b/absl/log/internal/log_sink_set.h new file mode 100644 index 00000000..88ab073b --- /dev/null +++ b/absl/log/internal/log_sink_set.h @@ -0,0 +1,54 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/log_sink_set.h +// ----------------------------------------------------------------------------- + +#ifndef ABSL_LOG_INTERNAL_LOG_SINK_SET_H_ +#define ABSL_LOG_INTERNAL_LOG_SINK_SET_H_ + +#include "absl/base/config.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// Returns true if a globally-registered `LogSink`'s `Send()` is currently +// being invoked on this thread. +bool ThreadIsLoggingToLogSink(); + +// This function may log to two sets of sinks: +// +// * If `extra_sinks_only` is true, it will dispatch only to `extra_sinks`. +// `LogMessage::ToSinkAlso` and `LogMessage::ToSinkOnly` are used to attach +// extra sinks to the entry. +// * Otherwise it will also log to the global sinks set. This set is managed +// by `absl::AddLogSink` and `absl::RemoveLogSink`. +void LogToSinks(const absl::LogEntry& entry, + absl::Span<absl::LogSink*> extra_sinks, bool extra_sinks_only); + +// Implementation for operations with log sink set. +void AddLogSink(absl::LogSink* sink); +void RemoveLogSink(absl::LogSink* sink); +void FlushLogSinks(); + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_LOG_SINK_SET_H_ diff --git a/absl/log/internal/nullguard.h b/absl/log/internal/nullguard.h new file mode 100644 index 00000000..147ca814 --- /dev/null +++ b/absl/log/internal/nullguard.h @@ -0,0 +1,56 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/nullguard.h +// ----------------------------------------------------------------------------- +// +// NullGuard exists such that NullGuard<T>::Guard(v) returns v, unless passed a +// nullptr_t, or a null char* or const char*, in which case it returns "(null)". +// This allows streaming NullGuard<T>::Guard(v) to an output stream without +// hitting undefined behavior for null values. + +#ifndef ABSL_LOG_INTERNAL_NULLGUARD_H_ +#define ABSL_LOG_INTERNAL_NULLGUARD_H_ + +#include <cstddef> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +template <typename T> +struct NullGuard final { + static const T& Guard(const T& v) { return v; } +}; +template <> +struct NullGuard<char*> final { + static const char* Guard(const char* v) { return v ? v : "(null)"; } +}; +template <> +struct NullGuard<const char*> final { + static const char* Guard(const char* v) { return v ? v : "(null)"; } +}; +template <> +struct NullGuard<std::nullptr_t> final { + static const char* Guard(const std::nullptr_t&) { return "(null)"; } +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_NULLGUARD_H_ diff --git a/absl/log/internal/nullstream.h b/absl/log/internal/nullstream.h new file mode 100644 index 00000000..80c62c9e --- /dev/null +++ b/absl/log/internal/nullstream.h @@ -0,0 +1,134 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/nullstream.h +// ----------------------------------------------------------------------------- +// +// Classes `NullStream`, `NullStreamMaybeFatal ` and `NullStreamFatal` +// implement a subset of the `LogMessage` API and are used instead when logging +// of messages has been disabled. + +#ifndef ABSL_LOG_INTERNAL_NULLSTREAM_H_ +#define ABSL_LOG_INTERNAL_NULLSTREAM_H_ + +#ifdef _WIN32 +#include <cstdlib> +#else +#include <unistd.h> +#endif +#include <ios> +#include <ostream> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// A `NullStream` implements the API of `LogMessage` (a few methods and +// `operator<<`) but does nothing. All methods are defined inline so the +// compiler can eliminate the whole instance and discard anything that's +// streamed in. +class NullStream { + public: + NullStream& AtLocation(absl::string_view, int) { return *this; } + template <typename SourceLocationType> + NullStream& AtLocation(SourceLocationType) { + return *this; + } + NullStream& NoPrefix() { return *this; } + NullStream& WithVerbosity(int) { return *this; } + template <typename TimeType> + NullStream& WithTimestamp(TimeType) { + return *this; + } + template <typename Tid> + NullStream& WithThreadID(Tid) { + return *this; + } + template <typename LogEntryType> + NullStream& WithMetadataFrom(const LogEntryType&) { + return *this; + } + NullStream& WithPerror() { return *this; } + template <typename LogSinkType> + NullStream& ToSinkAlso(LogSinkType*) { + return *this; + } + template <typename LogSinkType> + NullStream& ToSinkOnly(LogSinkType*) { + return *this; + } + template <typename LogSinkType> + NullStream& OutputToSink(LogSinkType*, bool) { + return *this; + } + NullStream& InternalStream() { return *this; } +}; +template <typename T> +inline NullStream& operator<<(NullStream& str, const T&) { + return str; +} +inline NullStream& operator<<(NullStream& str, + std::ostream& (*)(std::ostream& os)) { + return str; +} +inline NullStream& operator<<(NullStream& str, + std::ios_base& (*)(std::ios_base& os)) { + return str; +} + +// `NullStreamMaybeFatal` implements the process termination semantics of +// `LogMessage`, which is used for `DFATAL` severity and expression-defined +// severity e.g. `LOG(LEVEL(HowBadIsIt()))`. Like `LogMessage`, it terminates +// the process when destroyed if the passed-in severity equals `FATAL`. +class NullStreamMaybeFatal final : public NullStream { + public: + explicit NullStreamMaybeFatal(absl::LogSeverity severity) + : fatal_(severity == absl::LogSeverity::kFatal) {} + ~NullStreamMaybeFatal() { + if (fatal_) _exit(1); + } + + private: + bool fatal_; +}; + +// `NullStreamFatal` implements the process termination semantics of +// `LogMessageFatal`, which means it always terminates the process. `DFATAL` +// and expression-defined severity use `NullStreamMaybeFatal` above. +class NullStreamFatal final : public NullStream { + public: + NullStreamFatal() {} + // ABSL_ATTRIBUTE_NORETURN doesn't seem to work on destructors with msvc, so + // disable msvc's warning about the d'tor never returning. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4722) +#endif + ABSL_ATTRIBUTE_NORETURN ~NullStreamFatal() { _exit(1); } +#ifdef _MSC_VER +#pragma warning(pop) +#endif +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_GLOBALS_H_ diff --git a/absl/log/internal/stderr_log_sink_test.cc b/absl/log/internal/stderr_log_sink_test.cc new file mode 100644 index 00000000..763690d1 --- /dev/null +++ b/absl/log/internal/stderr_log_sink_test.cc @@ -0,0 +1,105 @@ +// +// Copyright 2022 The Abseil 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. + +#include <stdlib.h> + +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/log_severity.h" +#include "absl/log/globals.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/log.h" + +namespace { +using ::testing::AllOf; +using ::testing::HasSubstr; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +MATCHER_P2(HasSubstrTimes, substr, expected_count, "") { + int count = 0; + std::string::size_type pos = 0; + std::string needle(substr); + while ((pos = arg.find(needle, pos)) != std::string::npos) { + ++count; + pos += needle.size(); + } + + return count == expected_count; +} + +TEST(StderrLogSinkDeathTest, InfoMessagesInStderr) { + EXPECT_DEATH_IF_SUPPORTED( + { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); + LOG(INFO) << "INFO message"; + exit(1); + }, + "INFO message"); +} + +TEST(StderrLogSinkDeathTest, WarningMessagesInStderr) { + EXPECT_DEATH_IF_SUPPORTED( + { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); + LOG(WARNING) << "WARNING message"; + exit(1); + }, + "WARNING message"); +} + +TEST(StderrLogSinkDeathTest, ErrorMessagesInStderr) { + EXPECT_DEATH_IF_SUPPORTED( + { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); + LOG(ERROR) << "ERROR message"; + exit(1); + }, + "ERROR message"); +} + +TEST(StderrLogSinkDeathTest, FatalMessagesInStderr) { + char message[] = "FATAL message"; + char stacktrace[] = "*** Check failure stack trace: ***"; + + int expected_count = 1; + + EXPECT_DEATH_IF_SUPPORTED( + { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); + LOG(FATAL) << message; + }, + AllOf(HasSubstrTimes(message, expected_count), HasSubstr(stacktrace))); +} + +TEST(StderrLogSinkDeathTest, SecondaryFatalMessagesInStderr) { + auto MessageGen = []() -> std::string { + LOG(FATAL) << "Internal failure"; + return "External failure"; + }; + + EXPECT_DEATH_IF_SUPPORTED( + { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); + LOG(FATAL) << MessageGen(); + }, + "Internal failure"); +} + +} // namespace diff --git a/absl/log/internal/strip.h b/absl/log/internal/strip.h new file mode 100644 index 00000000..848c3867 --- /dev/null +++ b/absl/log/internal/strip.h @@ -0,0 +1,71 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/strip.h +// ----------------------------------------------------------------------------- +// + +#ifndef ABSL_LOG_INTERNAL_STRIP_H_ +#define ABSL_LOG_INTERNAL_STRIP_H_ + +#include "absl/base/log_severity.h" +#include "absl/log/internal/log_message.h" +#include "absl/log/internal/nullstream.h" + +// `ABSL_LOGGING_INTERNAL_LOG_*` evaluates to a temporary `LogMessage` object or +// to a related object with a compatible API but different behavior. This set +// of defines comes in three flavors: vanilla, plus two variants that strip some +// logging in subtly different ways for subtly different reasons (see below). +#if defined(STRIP_LOG) && STRIP_LOG +#define ABSL_LOGGING_INTERNAL_LOG_INFO ::absl::log_internal::NullStream() +#define ABSL_LOGGING_INTERNAL_LOG_WARNING ::absl::log_internal::NullStream() +#define ABSL_LOGGING_INTERNAL_LOG_ERROR ::absl::log_internal::NullStream() +#define ABSL_LOGGING_INTERNAL_LOG_FATAL ::absl::log_internal::NullStreamFatal() +#define ABSL_LOGGING_INTERNAL_LOG_QFATAL ::absl::log_internal::NullStreamFatal() +#define ABSL_LOGGING_INTERNAL_LOG_DFATAL \ + ::absl::log_internal::NullStreamMaybeFatal(::absl::kLogDebugFatal) +#define ABSL_LOGGING_INTERNAL_LOG_LEVEL(severity) \ + ::absl::log_internal::NullStreamMaybeFatal(log_internal_severity) +#define ABSL_LOG_INTERNAL_CHECK(failure_message) ABSL_LOGGING_INTERNAL_LOG_FATAL +#define ABSL_LOG_INTERNAL_QCHECK(failure_message) \ + ABSL_LOGGING_INTERNAL_LOG_QFATAL +#else // !defined(STRIP_LOG) || !STRIP_LOG +#define ABSL_LOGGING_INTERNAL_LOG_INFO \ + ::absl::log_internal::LogMessage(__FILE__, __LINE__, \ + ::absl::LogSeverity::kInfo) +#define ABSL_LOGGING_INTERNAL_LOG_WARNING \ + ::absl::log_internal::LogMessage(__FILE__, __LINE__, \ + ::absl::LogSeverity::kWarning) +#define ABSL_LOGGING_INTERNAL_LOG_ERROR \ + ::absl::log_internal::LogMessage(__FILE__, __LINE__, \ + ::absl::LogSeverity::kError) +#define ABSL_LOGGING_INTERNAL_LOG_FATAL \ + ::absl::log_internal::LogMessageFatal(__FILE__, __LINE__) +#define ABSL_LOGGING_INTERNAL_LOG_QFATAL \ + ::absl::log_internal::LogMessageQuietlyFatal(__FILE__, __LINE__) +#define ABSL_LOGGING_INTERNAL_LOG_DFATAL \ + ::absl::log_internal::LogMessage(__FILE__, __LINE__, ::absl::kLogDebugFatal) +#define ABSL_LOGGING_INTERNAL_LOG_LEVEL(severity) \ + ::absl::log_internal::LogMessage(__FILE__, __LINE__, log_internal_severity) +// These special cases dispatch to special-case constructors that allow us to +// avoid an extra function call and shrink non-LTO binaries by a percent or so. +#define ABSL_LOG_INTERNAL_CHECK(failure_message) \ + ::absl::log_internal::LogMessageFatal(__FILE__, __LINE__, failure_message) +#define ABSL_LOG_INTERNAL_QCHECK(failure_message) \ + ::absl::log_internal::LogMessageQuietlyFatal(__FILE__, __LINE__, \ + failure_message) +#endif // !defined(STRIP_LOG) || !STRIP_LOG + +#endif // ABSL_LOG_INTERNAL_STRIP_H_ diff --git a/absl/log/internal/test_actions.cc b/absl/log/internal/test_actions.cc new file mode 100644 index 00000000..41ca9887 --- /dev/null +++ b/absl/log/internal/test_actions.cc @@ -0,0 +1,68 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/internal/test_actions.h" + +#include <cassert> +#include <iostream> +#include <string> + +#include "absl/base/config.h" +#include "absl/log/internal/config.h" +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +void WriteToStderrWithFilename::operator()(const absl::LogEntry& entry) const { + std::cerr << message << " (file: " << entry.source_filename() << ")" + << std::endl; +} + +void WriteEntryToStderr::operator()(const absl::LogEntry& entry) const { + if (!message.empty()) std::cerr << message << std::endl; + + std::cerr << "LogEntry{\n" + << " source_filename: \"" + << absl::CHexEscape(entry.source_filename()) << "\"\n" + << " source_basename: \"" + << absl::CHexEscape(entry.source_basename()) << "\"\n" + << " source_line: " << entry.source_line() << "\n" + << " prefix: " << (entry.prefix() ? "true\n" : "false\n") + << " log_severity: " << entry.log_severity() << "\n" + << " timestamp: " << entry.timestamp() << "\n" + << " text_message: \"" << absl::CHexEscape(entry.text_message()) + << "\"\n verbosity: " << entry.verbosity() << "\n" + << "}" << std::endl; +} + +void WriteEntryToStderr::operator()(absl::LogSeverity severity, + absl::string_view filename, + absl::string_view log_message) const { + if (!message.empty()) std::cerr << message << std::endl; + + std::cerr << "LogEntry{\n" + << " source_filename: \"" << absl::CHexEscape(filename) << "\"\n" + << " log_severity: " << severity << "\n" + << " text_message: \"" << absl::CHexEscape(log_message) << "}" + << std::endl; +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/test_actions.h b/absl/log/internal/test_actions.h new file mode 100644 index 00000000..649a0505 --- /dev/null +++ b/absl/log/internal/test_actions.h @@ -0,0 +1,90 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/test_actions.h +// ----------------------------------------------------------------------------- +// +// This file declares Googletest's actions used in the Abseil Logging library +// unit tests. + +#ifndef ABSL_LOG_INTERNAL_TEST_ACTIONS_H_ +#define ABSL_LOG_INTERNAL_TEST_ACTIONS_H_ + +#include <iostream> +#include <ostream> +#include <string> + +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/log_entry.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// These actions are used by the child process in a death test. +// +// Expectations set in the child cannot cause test failure in the parent +// directly. Instead, the child can use these actions with +// `EXPECT_CALL`/`WillOnce` and `ON_CALL`/`WillByDefault` (for unexpected calls) +// to write messages to stderr that the parent can match against. +struct WriteToStderr final { + explicit WriteToStderr(absl::string_view m) : message(m) {} + std::string message; + + template <typename... Args> + void operator()(const Args&...) const { + std::cerr << message << std::endl; + } +}; + +struct WriteToStderrWithFilename final { + explicit WriteToStderrWithFilename(absl::string_view m) : message(m) {} + + std::string message; + + void operator()(const absl::LogEntry& entry) const; +}; + +struct WriteEntryToStderr final { + explicit WriteEntryToStderr(absl::string_view m) : message(m) {} + + std::string message = ""; + + void operator()(const absl::LogEntry& entry) const; + void operator()(absl::LogSeverity, absl::string_view, + absl::string_view) const; +}; + +// See the documentation for `DeathTestValidateExpectations` above. +// `DeathTestExpectedLogging` should be used once in a given death test, and the +// applicable severity level is the one that should be passed to +// `DeathTestValidateExpectations`. +inline WriteEntryToStderr DeathTestExpectedLogging() { + return WriteEntryToStderr{"Mock received expected entry:"}; +} + +// `DeathTestUnexpectedLogging` should be used zero or more times to mark +// messages that should not hit the logs as the process dies. +inline WriteEntryToStderr DeathTestUnexpectedLogging() { + return WriteEntryToStderr{"Mock received unexpected entry:"}; +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_TEST_ACTIONS_H_ diff --git a/absl/log/internal/test_helpers.cc b/absl/log/internal/test_helpers.cc new file mode 100644 index 00000000..bff5cc17 --- /dev/null +++ b/absl/log/internal/test_helpers.cc @@ -0,0 +1,82 @@ +// Copyright 2022 The Abseil 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. +// +#include "absl/log/internal/test_helpers.h" + +#ifdef __Fuchsia__ +#include <zircon/syscalls.h> +#endif + +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/globals.h" +#include "absl/log/initialize.h" +#include "absl/log/internal/globals.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// Returns false if the specified severity level is disabled by +// `ABSL_MIN_LOG_LEVEL` or `absl::MinLogLevel()`. +bool LoggingEnabledAt(absl::LogSeverity severity) { + return severity >= kAbslMinLogLevel && severity >= absl::MinLogLevel(); +} + +// ----------------------------------------------------------------------------- +// Googletest Death Test Predicates +// ----------------------------------------------------------------------------- + +#if GTEST_HAS_DEATH_TEST + +bool DiedOfFatal(int exit_status) { +#if defined(_WIN32) + // Depending on NDEBUG and (configuration?) MSVC's abort either results + // in error code 3 (SIGABRT) or error code 0x80000003 (breakpoint + // triggered). + return ::testing::ExitedWithCode(3)(exit_status & ~0x80000000); +#elif defined(__Fuchsia__) + // The Fuchsia death test implementation kill()'s the process when it detects + // an exception, so it should exit with the corresponding code. See + // FuchsiaDeathTest::Wait(). + return ::testing::ExitedWithCode(ZX_TASK_RETCODE_SYSCALL_KILL)(exit_status); +#elif defined(__ANDROID__) && defined(__aarch64__) + // These are all run under a qemu config that eats died-due-to-signal exit + // statuses. + return true; +#else + return ::testing::KilledBySignal(SIGABRT)(exit_status); +#endif +} + +bool DiedOfQFatal(int exit_status) { + return ::testing::ExitedWithCode(1)(exit_status); +} + +#endif + +// ----------------------------------------------------------------------------- +// Helper for Log inititalization in test +// ----------------------------------------------------------------------------- + +void LogTestEnvironment::SetUp() { + if (!absl::log_internal::IsInitialized()) { + absl::InitializeLog(); + } +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/test_helpers.h b/absl/log/internal/test_helpers.h new file mode 100644 index 00000000..fd06e295 --- /dev/null +++ b/absl/log/internal/test_helpers.h @@ -0,0 +1,71 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/test_helpers.h +// ----------------------------------------------------------------------------- +// +// This file declares testing helpers for the logging library. + +#ifndef ABSL_LOG_INTERNAL_TEST_HELPERS_H_ +#define ABSL_LOG_INTERNAL_TEST_HELPERS_H_ + +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/globals.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// `ABSL_MIN_LOG_LEVEL` can't be used directly since it is not always defined. +constexpr auto kAbslMinLogLevel = +#ifdef ABSL_MIN_LOG_LEVEL + static_cast<absl::LogSeverityAtLeast>(ABSL_MIN_LOG_LEVEL); +#else + absl::LogSeverityAtLeast::kInfo; +#endif + +// Returns false if the specified severity level is disabled by +// `ABSL_MIN_LOG_LEVEL` or `absl::MinLogLevel()`. +bool LoggingEnabledAt(absl::LogSeverity severity); + +// ----------------------------------------------------------------------------- +// Googletest Death Test Predicates +// ----------------------------------------------------------------------------- + +#if GTEST_HAS_DEATH_TEST + +bool DiedOfFatal(int exit_status); +bool DiedOfQFatal(int exit_status); + +#endif + +// ----------------------------------------------------------------------------- +// Helper for Log inititalization in test +// ----------------------------------------------------------------------------- + +class LogTestEnvironment : public ::testing::Environment { + public: + ~LogTestEnvironment() override = default; + + void SetUp() override; +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_TEST_HELPERS_H_ diff --git a/absl/log/internal/test_matchers.cc b/absl/log/internal/test_matchers.cc new file mode 100644 index 00000000..ee32617b --- /dev/null +++ b/absl/log/internal/test_matchers.cc @@ -0,0 +1,168 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/internal/test_matchers.h" + +#include <sstream> +#include <string> +#include <utility> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/log/internal/config.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/strings/string_view.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +::testing::Matcher<const absl::LogEntry&> SourceFilename( + const ::testing::Matcher<absl::string_view>& source_filename) { + return Property("source_filename", &absl::LogEntry::source_filename, + source_filename); +} + +::testing::Matcher<const absl::LogEntry&> SourceBasename( + const ::testing::Matcher<absl::string_view>& source_basename) { + return Property("source_basename", &absl::LogEntry::source_basename, + source_basename); +} + +::testing::Matcher<const absl::LogEntry&> SourceLine( + const ::testing::Matcher<int>& source_line) { + return Property("source_line", &absl::LogEntry::source_line, source_line); +} + +::testing::Matcher<const absl::LogEntry&> Prefix( + const ::testing::Matcher<bool>& prefix) { + return Property("prefix", &absl::LogEntry::prefix, prefix); +} + +::testing::Matcher<const absl::LogEntry&> LogSeverity( + const ::testing::Matcher<absl::LogSeverity>& log_severity) { + return Property("log_severity", &absl::LogEntry::log_severity, log_severity); +} + +::testing::Matcher<const absl::LogEntry&> Timestamp( + const ::testing::Matcher<absl::Time>& timestamp) { + return Property("timestamp", &absl::LogEntry::timestamp, timestamp); +} + +::testing::Matcher<const absl::LogEntry&> TimestampInMatchWindow() { + return Property("timestamp", &absl::LogEntry::timestamp, + ::testing::AllOf(::testing::Ge(absl::Now()), + ::testing::Truly([](absl::Time arg) { + return arg <= absl::Now(); + }))); +} + +::testing::Matcher<const absl::LogEntry&> ThreadID( + const ::testing::Matcher<absl::LogEntry::tid_t>& tid) { + return Property("tid", &absl::LogEntry::tid, tid); +} + +::testing::Matcher<const absl::LogEntry&> TextMessageWithPrefixAndNewline( + const ::testing::Matcher<absl::string_view>& + text_message_with_prefix_and_newline) { + return Property("text_message_with_prefix_and_newline", + &absl::LogEntry::text_message_with_prefix_and_newline, + text_message_with_prefix_and_newline); +} + +::testing::Matcher<const absl::LogEntry&> TextMessageWithPrefix( + const ::testing::Matcher<absl::string_view>& text_message_with_prefix) { + return Property("text_message_with_prefix", + &absl::LogEntry::text_message_with_prefix, + text_message_with_prefix); +} + +::testing::Matcher<const absl::LogEntry&> TextMessage( + const ::testing::Matcher<absl::string_view>& text_message) { + return Property("text_message", &absl::LogEntry::text_message, text_message); +} + +::testing::Matcher<const absl::LogEntry&> TextPrefix( + const ::testing::Matcher<absl::string_view>& text_prefix) { + return ResultOf( + [](const absl::LogEntry& entry) { + absl::string_view msg = entry.text_message_with_prefix(); + msg.remove_suffix(entry.text_message().size()); + return msg; + }, + text_prefix); +} + +::testing::Matcher<const absl::LogEntry&> Verbosity( + const ::testing::Matcher<int>& verbosity) { + return Property("verbosity", &absl::LogEntry::verbosity, verbosity); +} + +::testing::Matcher<const absl::LogEntry&> Stacktrace( + const ::testing::Matcher<absl::string_view>& stacktrace) { + return Property("stacktrace", &absl::LogEntry::stacktrace, stacktrace); +} + +class MatchesOstreamImpl final + : public ::testing::MatcherInterface<absl::string_view> { + public: + explicit MatchesOstreamImpl(std::string expected) + : expected_(std::move(expected)) {} + bool MatchAndExplain(absl::string_view actual, + ::testing::MatchResultListener*) const override { + return actual == expected_; + } + void DescribeTo(std::ostream* os) const override { + *os << "matches the contents of the ostringstream, which are \"" + << expected_ << "\""; + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not match the contents of the ostringstream, which are \"" + << expected_ << "\""; + } + + private: + const std::string expected_; +}; +::testing::Matcher<absl::string_view> MatchesOstream( + const std::ostringstream& stream) { + return ::testing::MakeMatcher(new MatchesOstreamImpl(stream.str())); +} + +// We need to validate what is and isn't logged as the process dies due to +// `FATAL`, `QFATAL`, `CHECK`, etc., but assertions inside a death test +// subprocess don't directly affect the pass/fail status of the parent process. +// Instead, we use the mock actions `DeathTestExpectedLogging` and +// `DeathTestUnexpectedLogging` to write specific phrases to `stderr` that we +// can validate in the parent process using this matcher. +::testing::Matcher<const std::string&> DeathTestValidateExpectations() { + if (log_internal::LoggingEnabledAt(absl::LogSeverity::kFatal)) { + return ::testing::Matcher<const std::string&>(::testing::AllOf( + ::testing::HasSubstr("Mock received expected entry"), + Not(::testing::HasSubstr("Mock received unexpected entry")))); + } + // If `FATAL` logging is disabled, neither message should have been written. + return ::testing::Matcher<const std::string&>(::testing::AllOf( + Not(::testing::HasSubstr("Mock received expected entry")), + Not(::testing::HasSubstr("Mock received unexpected entry")))); +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/test_matchers.h b/absl/log/internal/test_matchers.h new file mode 100644 index 00000000..b8179ccc --- /dev/null +++ b/absl/log/internal/test_matchers.h @@ -0,0 +1,90 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/test_matchers.h +// ----------------------------------------------------------------------------- +// +// This file declares Googletest's matchers used in the Abseil Logging library +// unit tests. + +#ifndef ABSL_LOG_INTERNAL_TEST_MATCHERS_H_ +#define ABSL_LOG_INTERNAL_TEST_MATCHERS_H_ + +#include <iosfwd> +#include <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/internal/config.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/log_entry.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// These matchers correspond to the components of `absl::LogEntry`. +::testing::Matcher<const absl::LogEntry&> SourceFilename( + const ::testing::Matcher<absl::string_view>& source_filename); +::testing::Matcher<const absl::LogEntry&> SourceBasename( + const ::testing::Matcher<absl::string_view>& source_basename); +// Be careful with this one; multi-line statements using `__LINE__` evaluate +// differently on different platforms. In particular, the MSVC implementation +// of `EXPECT_DEATH` returns the line number of the macro expansion to all lines +// within the code block that's expected to die. +::testing::Matcher<const absl::LogEntry&> SourceLine( + const ::testing::Matcher<int>& source_line); +::testing::Matcher<const absl::LogEntry&> Prefix( + const ::testing::Matcher<bool>& prefix); +::testing::Matcher<const absl::LogEntry&> LogSeverity( + const ::testing::Matcher<absl::LogSeverity>& log_severity); +::testing::Matcher<const absl::LogEntry&> Timestamp( + const ::testing::Matcher<absl::Time>& timestamp); +// Matches if the `LogEntry`'s timestamp falls after the instantiation of this +// matcher and before its execution, as is normal when used with EXPECT_CALL. +::testing::Matcher<const absl::LogEntry&> TimestampInMatchWindow(); +::testing::Matcher<const absl::LogEntry&> ThreadID( + const ::testing::Matcher<absl::LogEntry::tid_t>&); +::testing::Matcher<const absl::LogEntry&> TextMessageWithPrefixAndNewline( + const ::testing::Matcher<absl::string_view>& + text_message_with_prefix_and_newline); +::testing::Matcher<const absl::LogEntry&> TextMessageWithPrefix( + const ::testing::Matcher<absl::string_view>& text_message_with_prefix); +::testing::Matcher<const absl::LogEntry&> TextMessage( + const ::testing::Matcher<absl::string_view>& text_message); +::testing::Matcher<const absl::LogEntry&> TextPrefix( + const ::testing::Matcher<absl::string_view>& text_prefix); +::testing::Matcher<const absl::LogEntry&> Verbosity( + const ::testing::Matcher<int>& verbosity); +::testing::Matcher<const absl::LogEntry&> Stacktrace( + const ::testing::Matcher<absl::string_view>& stacktrace); +// Behaves as `Eq(stream.str())`, but produces better failure messages. +::testing::Matcher<absl::string_view> MatchesOstream( + const std::ostringstream& stream); +::testing::Matcher<const std::string&> DeathTestValidateExpectations(); + +// This feature coming soon =). +#define ENCODED_MESSAGE(message_matcher) ::testing::_ + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_TEST_MATCHERS_H_ diff --git a/absl/log/internal/voidify.h b/absl/log/internal/voidify.h new file mode 100644 index 00000000..8f62da20 --- /dev/null +++ b/absl/log/internal/voidify.h @@ -0,0 +1,44 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/internal/voidify.h +// ----------------------------------------------------------------------------- +// +// This class is used to explicitly ignore values in the conditional logging +// macros. This avoids compiler warnings like "value computed is not used" and +// "statement has no effect". + +#ifndef ABSL_LOG_INTERNAL_VOIDIFY_H_ +#define ABSL_LOG_INTERNAL_VOIDIFY_H_ + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +class Voidify final { + public: + // This has to be an operator with a precedence lower than << but higher than + // ?: + template <typename T> + void operator&&(const T&) const&& {} +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_VOIDIFY_H_ diff --git a/absl/log/log.h b/absl/log/log.h new file mode 100644 index 00000000..13c4938f --- /dev/null +++ b/absl/log/log.h @@ -0,0 +1,439 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/log.h +// ----------------------------------------------------------------------------- +// +// This header declares a family of LOG macros. +// +// Basic invocation looks like this: +// +// LOG(INFO) << "Found " << num_cookies << " cookies"; +// +// Most `LOG` macros take a severity level argument. The severity levels are +// `INFO`, `WARNING`, `ERROR`, and `FATAL`. They are defined +// in absl/base/log_severity.h. +// * The `FATAL` severity level terminates the program with a stack trace after +// logging its message. Error handlers registered with `RunOnFailure` +// (process_state.h) are run, but exit handlers registered with `atexit(3)` +// are not. +// * The `QFATAL` pseudo-severity level is equivalent to `FATAL` but triggers +// quieter termination messages, e.g. without a full stack trace, and skips +// running registered error handlers. +// Some preprocessor shenanigans are used to ensure that e.g. `LOG(INFO)` has +// the same meaning even if a local symbol or preprocessor macro named `INFO` is +// defined. To specify a severity level using an expression instead of a +// literal, use `LEVEL(expr)`. +// Example: +// +// LOG(LEVEL(stale ? absl::LogSeverity::kWarning : absl::LogSeverity::kInfo)) +// << "Cookies are " << days << " days old"; + +// `LOG` macros evaluate to an unterminated statement. The value at the end of +// the statement supports some chainable methods: +// +// * .AtLocation(absl::string_view file, int line) +// .AtLocation(absl::SourceLocation loc) +// Overrides the location inferred from the callsite. The string pointed to +// by `file` must be valid until the end of the statement. +// * .NoPrefix() +// Omits the prefix from this line. The prefix includes metadata about the +// logged data such as source code location and timestamp. +// * .WithTimestamp(absl::Time timestamp) +// Uses the specified timestamp instead of one collected at the time of +// execution. +// * .WithThreadID(absl::LogEntry::tid_t tid) +// Uses the specified thread ID instead of one collected at the time of +// execution. +// * .WithMetadataFrom(const absl::LogEntry &entry) +// Copies all metadata (but no data) from the specified `absl::LogEntry`. +// This can be used to change the severity of a message, but it has some +// limitations: +// * `ABSL_MIN_LOG_LEVEL` is evaluated against the severity passed into +// `LOG` (or the implicit `FATAL` level of `CHECK`). +// * `LOG(FATAL)` and `CHECK` terminate the process unconditionally, even if +// the severity is changed later. +// `.WithMetadataFrom(entry)` should almost always be used in combination +// with `LOG(LEVEL(entry.log_severity()))`. +// * .WithPerror() +// Appends to the logged message a colon, a space, a textual description of +// the current value of `errno` (as by `strerror(3)`), and the numerical +// value of `errno`. +// * .ToSinkAlso(absl::LogSink* sink) +// Sends this message to `*sink` in addition to whatever other sinks it +// would otherwise have been sent to. `sink` must not be null. +// * .ToSinkOnly(absl::LogSink* sink) +// Sends this message to `*sink` and no others. `sink` must not be null. +// +// No interfaces in this header are async-signal-safe; their use in signal +// handlers is unsupported and may deadlock your program or eat your lunch. +// +// Many logging statements are inherently conditional. For example, +// `LOG_IF(INFO, !foo)` does nothing if `foo` is true. Even seemingly +// unconditional statements like `LOG(INFO)` might be disabled at +// compile-time to minimize binary size or for security reasons. +// +// * Except for the condition in a `CHECK` or `QCHECK` statement, programs must +// not rely on evaluation of expressions anywhere in logging statements for +// correctness. For example, this is ok: +// +// CHECK((fp = fopen("config.ini", "r")) != nullptr); +// +// But this is probably not ok: +// +// LOG(INFO) << "Server status: " << StartServerAndReturnStatusString(); +// +// The example below is bad too; the `i++` in the `LOG_IF` condition might +// not be evaluated, resulting in an infinite loop: +// +// for (int i = 0; i < 1000000;) +// LOG_IF(INFO, i++ % 1000 == 0) << "Still working..."; +// +// * Except where otherwise noted, conditions which cause a statement not to log +// also cause expressions not to be evaluated. Programs may rely on this for +// performance reasons, e.g. by streaming the result of an expensive function +// call into a `DLOG` or `LOG_EVERY_N` statement. +// * Care has been taken to ensure that expressions are parsed by the compiler +// even if they are never evaluated. This means that syntax errors will be +// caught and variables will be considered used for the purposes of +// unused-variable diagnostics. For example, this statement won't compile +// even if `INFO`-level logging has been compiled out: +// +// int number_of_cakes = 40; +// LOG(INFO) << "Number of cakes: " << number_of_cake; // Note the typo! +// +// Similarly, this won't produce unused-variable compiler diagnostics even +// if `INFO`-level logging is compiled out: +// +// { +// char fox_line1[] = "Hatee-hatee-hatee-ho!"; +// LOG_IF(ERROR, false) << "The fox says " << fox_line1; +// char fox_line2[] = "A-oo-oo-oo-ooo!"; +// LOG(INFO) << "The fox also says " << fox_line2; +// } +// +// This error-checking is not perfect; for example, symbols that have been +// declared but not defined may not produce link errors if used in logging +// statements that compile away. +// +// Expressions streamed into these macros are formatted using `operator<<` just +// as they would be if streamed into a `std::ostream`, however it should be +// noted that their actual type is unspecified. +// +// To implement a custom formatting operator for a type you own, define +// `std::ostream& operator<<(std::ostream&, ...)` in your type's namespace (for +// ADL) just as you would to stream it to `std::cout`. +// +// Those macros that support streaming honor output manipulators and `fmtflag` +// changes that output data (e.g. `std::ends`) or control formatting of data +// (e.g. `std::hex` and `std::fixed`), however flushing such a stream is +// ignored. The message produced by a log statement is sent to registered +// `absl::LogSink` instances at the end of the statement; those sinks are +// responsible for their own flushing (e.g. to disk) semantics. +// +// Flag settings are not carried over from one `LOG` statement to the next; this +// is a bit different than e.g. `std::cout`: +// +// LOG(INFO) << std::hex << 0xdeadbeef; // logs "0xdeadbeef" +// LOG(INFO) << 0xdeadbeef; // logs "3735928559" + +#ifndef ABSL_LOG_LOG_H_ +#define ABSL_LOG_LOG_H_ + +#include "absl/log/internal/conditions.h" +#include "absl/log/internal/log_message.h" +#include "absl/log/internal/strip.h" + +// LOG() +// +// `LOG` takes a single argument which is a severity level. Data streamed in +// comprise the logged message. +// Example: +// +// LOG(INFO) << "Found " << num_cookies << " cookies"; +#define LOG(severity) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, true) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +// PLOG() +// +// `PLOG` behaves like `LOG` except that a description of the current state of +// `errno` is appended to the streamed message. +#define PLOG(severity) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, true) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ + .WithPerror() + +// DLOG() +// +// `DLOG` behaves like `LOG` in debug mode (i.e. `#ifndef NDEBUG`). Otherwise +// it compiles away and does nothing. Note that `DLOG(FATAL)` does not +// terminate the program if `NDEBUG` is defined. +#ifndef NDEBUG +#define DLOG(severity) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, true) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() +#else +#define DLOG(severity) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, false) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() +#endif + +// `LOG_IF` and friends add a second argument which specifies a condition. If +// the condition is false, nothing is logged. +// Example: +// +// LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies"; +// +// There is no `VLOG_IF` because the order of evaluation of the arguments is +// ambiguous and the alternate spelling with an `if`-statement is trivial. +#define LOG_IF(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, condition) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() +#define PLOG_IF(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, condition) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ + .WithPerror() + +#ifndef NDEBUG +#define DLOG_IF(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, condition) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() +#else +#define DLOG_IF(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, false && (condition)) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() +#endif + +// LOG_EVERY_N +// +// An instance of `LOG_EVERY_N` increments a hidden zero-initialized counter +// every time execution passes through it and logs the specified message when +// the counter's value is a multiple of `n`, doing nothing otherwise. Each +// instance has its own counter. The counter's value can be logged by streaming +// the symbol `COUNTER`. `LOG_EVERY_N` is thread-safe. +// Example: +// +// LOG_EVERY_N(WARNING, 1000) << "Got a packet with a bad CRC (" << COUNTER +// << " total)"; +#define LOG_EVERY_N(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(EveryN, n) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +// LOG_FIRST_N +// +// `LOG_FIRST_N` behaves like `LOG_EVERY_N` except that the specified message is +// logged when the counter's value is less than `n`. `LOG_FIRST_N` is +// thread-safe. +#define LOG_FIRST_N(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(FirstN, n) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +// LOG_EVERY_POW_2 +// +// `LOG_EVERY_POW_2` behaves like `LOG_EVERY_N` except that the specified +// message is logged when the counter's value is a power of 2. +// `LOG_EVERY_POW_2` is thread-safe. +#define LOG_EVERY_POW_2(severity) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(EveryPow2) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +// LOG_EVERY_N_SEC +// +// An instance of `LOG_EVERY_N_SEC` uses a hidden state variable to log the +// specified message at most once every `n_seconds`. A hidden counter of +// executions (whether a message is logged or not) is also maintained and can be +// logged by streaming the symbol `COUNTER`. `LOG_EVERY_N_SEC` is thread-safe. +// Example: +// +// LOG_EVERY_N_SEC(INFO, 2.5) << "Got " << COUNTER << " cookies so far"; +#define LOG_EVERY_N_SEC(severity, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(EveryNSec, n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define PLOG_EVERY_N(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(EveryN, n) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ + .WithPerror() + +#define PLOG_FIRST_N(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(FirstN, n) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ + .WithPerror() + +#define PLOG_EVERY_POW_2(severity) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(EveryPow2) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ + .WithPerror() + +#define PLOG_EVERY_N_SEC(severity, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(EveryNSec, n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ + .WithPerror() + +#ifndef NDEBUG +#define DLOG_EVERY_N(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ + (EveryN, n) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define DLOG_FIRST_N(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ + (FirstN, n) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define DLOG_EVERY_POW_2(severity) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ + (EveryPow2) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define DLOG_EVERY_N_SEC(severity, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ + (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#else // def NDEBUG +#define DLOG_EVERY_N(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ + (EveryN, n) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define DLOG_FIRST_N(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ + (FirstN, n) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define DLOG_EVERY_POW_2(severity) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ + (EveryPow2) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define DLOG_EVERY_N_SEC(severity, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ + (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() +#endif // def NDEBUG + +#define VLOG_EVERY_N(verbose_level, n) \ + for (int absl_logging_internal_verbose_level = (verbose_level), \ + absl_logging_internal_log_loop = 1; \ + absl_logging_internal_log_loop; absl_logging_internal_log_loop = 0) \ + ABSL_LOG_INTERNAL_CONDITION_INFO( \ + STATEFUL, VLOG_IS_ON(absl_logging_internal_verbose_level)) \ + (EveryN, n) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ + absl_logging_internal_verbose_level) + +#define VLOG_FIRST_N(verbose_level, n) \ + for (int absl_logging_internal_verbose_level = (verbose_level), \ + absl_logging_internal_log_loop = 1; \ + absl_logging_internal_log_loop; absl_logging_internal_log_loop = 0) \ + ABSL_LOG_INTERNAL_CONDITION_INFO( \ + STATEFUL, VLOG_IS_ON(absl_logging_internal_verbose_level)) \ + (FirstN, n) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ + absl_logging_internal_verbose_level) + +#define VLOG_EVERY_POW_2(verbose_level) \ + for (int absl_logging_internal_verbose_level = (verbose_level), \ + absl_logging_internal_log_loop = 1; \ + absl_logging_internal_log_loop; absl_logging_internal_log_loop = 0) \ + ABSL_LOG_INTERNAL_CONDITION_INFO( \ + STATEFUL, VLOG_IS_ON(absl_logging_internal_verbose_level)) \ + (EveryPow2) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ + absl_logging_internal_verbose_level) + +#define VLOG_EVERY_N_SEC(verbose_level, n_seconds) \ + for (int absl_logging_internal_verbose_level = (verbose_level), \ + absl_logging_internal_log_loop = 1; \ + absl_logging_internal_log_loop; absl_logging_internal_log_loop = 0) \ + ABSL_LOG_INTERNAL_CONDITION_INFO( \ + STATEFUL, VLOG_IS_ON(absl_logging_internal_verbose_level)) \ + (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream() \ + .WithVerbosity(absl_logging_internal_verbose_level) + +// `LOG_IF_EVERY_N` and friends behave as the corresponding `LOG_EVERY_N` +// but neither increment a counter nor log a message if condition is false (as +// `LOG_IF`). +// Example: +// +// LOG_IF_EVERY_N(INFO, (size > 1024), 10) << "Got the " << COUNTER +// << "th big cookie"; +#define LOG_IF_EVERY_N(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryN, n) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define LOG_IF_FIRST_N(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(FirstN, n) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define LOG_IF_EVERY_POW_2(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryPow2) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define LOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryNSec, \ + n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define PLOG_IF_EVERY_N(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryN, n) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ + .WithPerror() + +#define PLOG_IF_FIRST_N(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(FirstN, n) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ + .WithPerror() + +#define PLOG_IF_EVERY_POW_2(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryPow2) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ + .WithPerror() + +#define PLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryNSec, \ + n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ + .WithPerror() + +#ifndef NDEBUG +#define DLOG_IF_EVERY_N(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryN, n) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define DLOG_IF_FIRST_N(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(FirstN, n) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define DLOG_IF_EVERY_POW_2(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryPow2) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define DLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryNSec, \ + n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#else // def NDEBUG +#define DLOG_IF_EVERY_N(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, false && (condition))( \ + EveryN, n) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define DLOG_IF_FIRST_N(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, false && (condition))( \ + FirstN, n) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define DLOG_IF_EVERY_POW_2(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, false && (condition))( \ + EveryPow2) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() + +#define DLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, false && (condition))( \ + EveryNSec, n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() +#endif // def NDEBUG + +#endif // ABSL_LOG_LOG_H_ diff --git a/absl/log/log_benchmark.cc b/absl/log/log_benchmark.cc new file mode 100644 index 00000000..45d9a5d6 --- /dev/null +++ b/absl/log/log_benchmark.cc @@ -0,0 +1,97 @@ +// Copyright 2022 The Abseil 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. + +#include "absl/base/attributes.h" +#include "absl/base/log_severity.h" +#include "absl/flags/flag.h" +#include "absl/log/check.h" +#include "absl/log/globals.h" +#include "absl/log/log.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" +#include "absl/log/log_sink_registry.h" +#include "benchmark/benchmark.h" + +namespace { + +class NullLogSink : public absl::LogSink { + public: + NullLogSink() { absl::AddLogSink(this); } + + ~NullLogSink() override { absl::RemoveLogSink(this); } + + void Send(const absl::LogEntry&) override {} +}; + +constexpr int x = -1; + +void BM_SuccessfulBinaryCheck(benchmark::State& state) { + int n = 0; + while (state.KeepRunningBatch(8)) { + CHECK_GE(n, x); + CHECK_GE(n, x); + CHECK_GE(n, x); + CHECK_GE(n, x); + CHECK_GE(n, x); + CHECK_GE(n, x); + CHECK_GE(n, x); + CHECK_GE(n, x); + ++n; + } + benchmark::DoNotOptimize(n); +} +BENCHMARK(BM_SuccessfulBinaryCheck); + +static void BM_SuccessfulUnaryCheck(benchmark::State& state) { + int n = 0; + while (state.KeepRunningBatch(8)) { + CHECK(n >= x); + CHECK(n >= x); + CHECK(n >= x); + CHECK(n >= x); + CHECK(n >= x); + CHECK(n >= x); + CHECK(n >= x); + CHECK(n >= x); + ++n; + } + benchmark::DoNotOptimize(n); +} +BENCHMARK(BM_SuccessfulUnaryCheck); + +static void BM_DisabledLogOverhead(benchmark::State& state) { + absl::ScopedStderrThreshold disable_stderr_logging( + absl::LogSeverityAtLeast::kInfinity); + absl::log_internal::ScopedMinLogLevel scoped_min_log_level( + absl::LogSeverityAtLeast::kInfinity); + for (auto _ : state) { + LOG(INFO); + } +} +BENCHMARK(BM_DisabledLogOverhead); + +static void BM_EnabledLogOverhead(benchmark::State& state) { + absl::ScopedStderrThreshold stderr_logging( + absl::LogSeverityAtLeast::kInfinity); + absl::log_internal::ScopedMinLogLevel scoped_min_log_level( + absl::LogSeverityAtLeast::kInfo); + ABSL_ATTRIBUTE_UNUSED NullLogSink null_sink; + for (auto _ : state) { + LOG(INFO); + } +} +BENCHMARK(BM_EnabledLogOverhead); + +} // namespace + diff --git a/absl/log/log_entry.cc b/absl/log/log_entry.cc new file mode 100644 index 00000000..19c3b3f1 --- /dev/null +++ b/absl/log/log_entry.cc @@ -0,0 +1,29 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/log_entry.h" + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL +constexpr int LogEntry::kNoVerbosityLevel; +constexpr int LogEntry::kNoVerboseLevel; +#endif + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/log_entry.h b/absl/log/log_entry.h new file mode 100644 index 00000000..d1f500ca --- /dev/null +++ b/absl/log/log_entry.h @@ -0,0 +1,163 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/log_entry.h +// ----------------------------------------------------------------------------- +// +// This header declares `class absl::LogEntry`, which represents a log record as +// passed to `LogSink::Send`. Data returned by pointer or by reference or by +// `absl::string_view` must be copied if they are needed after the lifetime of +// the `absl::LogEntry`. + +#ifndef ABSL_LOG_LOG_ENTRY_H_ +#define ABSL_LOG_LOG_ENTRY_H_ + +#include <cstddef> +#include <string> + +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/internal/config.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace log_internal { +// Test only friend. +class LogEntryTestPeer; +class LogMessage; +} // namespace log_internal + +// LogEntry +// +// Represents a single entry in a log, i.e., one log message. +// +// LogEntry is copyable and thread-compatible. +class LogEntry final { + public: + using tid_t = log_internal::Tid; + + // For non-verbose log entries, `verbosity()` returns `kNoVerbosityLevel`. + static constexpr int kNoVerbosityLevel = -1; + static constexpr int kNoVerboseLevel = -1; // TO BE removed + + LogEntry(const LogEntry&) = default; + LogEntry& operator=(const LogEntry&) = default; + + // Source file and line where the log message occurred. + // Take special care not to dereference the pointers returned by + // source_filename() and source_basename() after the lifetime of the + // `LogEntry`. This will usually work, because these are usually backed by a + // statically allocated char array obtained from the `__FILE__` macro, but + // it is nevertheless incorrect and will be broken by statements like + // `LOG(INFO).AtLocation(...)` (see above). If you need the data later, you + // must copy it. + absl::string_view source_filename() const { return full_filename_; } + absl::string_view source_basename() const { return base_filename_; } + int source_line() const { return line_; } + + // LogEntry::prefix() + // + // True unless cleared by LOG(...).NoPrefix(), which indicates suppression of + // the line prefix containing metadata like file, line, timestamp, etc. + bool prefix() const { return prefix_; } + + // LogEntry::log_severity() + // + // Returns this LogEntry's severity. + absl::LogSeverity log_severity() const { return severity_; } + + // LogEntry::verbosity() + // + // Returns this LogEntry's verbosity, or kNoVerbosityLevel for a non-verbose + // LogEntry. + int verbosity() const { return verbose_level_; } + + // LogEntry::timestamp() + // + // Returns the time at which this LogEntry was written. + absl::Time timestamp() const { return timestamp_; } + + // LogEntry::tid() + // + // Returns the id of the thread that wrote this LogEntry. + tid_t tid() const { return tid_; } + + // Text-formatted version of the log message. An underlying buffer holds: + // + // * A prefix formed by formatting metadata (timestamp, filename, line number, + // etc.) + // * The streamed data + // * A newline + // * A nul terminator + // + // These methods give access to the most commonly-used substrings of the + // buffer's contents. Other combinations can be obtained with substring + // arithmetic. + absl::string_view text_message_with_prefix_and_newline() const { + return absl::string_view( + text_message_with_prefix_and_newline_and_nul_.data(), + text_message_with_prefix_and_newline_and_nul_.size() - 1); + } + absl::string_view text_message_with_prefix() const { + return absl::string_view( + text_message_with_prefix_and_newline_and_nul_.data(), + text_message_with_prefix_and_newline_and_nul_.size() - 2); + } + absl::string_view text_message_with_newline() const { + return absl::string_view( + text_message_with_prefix_and_newline_and_nul_.data() + prefix_len_, + text_message_with_prefix_and_newline_and_nul_.size() - prefix_len_ - 1); + } + absl::string_view text_message() const { + return absl::string_view( + text_message_with_prefix_and_newline_and_nul_.data() + prefix_len_, + text_message_with_prefix_and_newline_and_nul_.size() - prefix_len_ - 2); + } + const char* text_message_with_prefix_and_newline_c_str() const { + return text_message_with_prefix_and_newline_and_nul_.data(); + } + + // LogEntry::stacktrace() + // + // Optional stacktrace, e.g., for `FATAL` logs. + absl::string_view stacktrace() const { return stacktrace_; } + + private: + LogEntry() = default; + + absl::string_view full_filename_; + absl::string_view base_filename_; + int line_; + bool prefix_; + absl::LogSeverity severity_; + int verbose_level_; // >=0 for `VLOG`, etc.; otherwise `kNoVerbosityLevel`. + absl::Time timestamp_; + tid_t tid_; + absl::Span<const char> text_message_with_prefix_and_newline_and_nul_; + size_t prefix_len_; + std::string stacktrace_; + + friend class log_internal::LogEntryTestPeer; + friend class log_internal::LogMessage; +}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_LOG_ENTRY_H_ diff --git a/absl/log/log_entry_test.cc b/absl/log/log_entry_test.cc new file mode 100644 index 00000000..b19794e4 --- /dev/null +++ b/absl/log/log_entry_test.cc @@ -0,0 +1,432 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/log_entry.h" + +#include <stddef.h> +#include <stdint.h> + +#include <cstring> +#include <limits> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/internal/log_format.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "absl/time/civil_time.h" +#include "absl/time/time.h" +#include "absl/types/span.h" + +namespace { + +using ::absl::log_internal::LogEntryTestPeer; +using ::testing::Eq; +using ::testing::IsTrue; +using ::testing::StartsWith; +using ::testing::StrEq; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +// Copies into `dst` as many bytes of `src` as will fit, then truncates the +// copied bytes from the front of `dst` and returns the number of bytes written. +size_t AppendTruncated(absl::string_view src, absl::Span<char>& dst) { + if (src.size() > dst.size()) src = src.substr(0, dst.size()); + memcpy(dst.data(), src.data(), src.size()); + dst.remove_prefix(src.size()); + return src.size(); +} + +} // namespace + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +class LogEntryTestPeer { + public: + LogEntryTestPeer(absl::string_view base_filename, int line, bool prefix, + absl::LogSeverity severity, absl::string_view timestamp, + absl::LogEntry::tid_t tid, absl::string_view text_message) + : buf_(15000, '\0') { + entry_.base_filename_ = base_filename; + entry_.line_ = line; + entry_.prefix_ = prefix; + entry_.severity_ = severity; + std::string time_err; + EXPECT_THAT( + absl::ParseTime("%Y-%m-%d%ET%H:%M:%E*S", timestamp, + absl::LocalTimeZone(), &entry_.timestamp_, &time_err), + IsTrue()) + << "Failed to parse time " << timestamp << ": " << time_err; + entry_.tid_ = tid; + std::pair<absl::string_view, std::string> timestamp_bits = + absl::StrSplit(timestamp, absl::ByChar('.')); + EXPECT_THAT(absl::ParseCivilTime(timestamp_bits.first, &ci_.cs), IsTrue()) + << "Failed to parse time " << timestamp_bits.first; + timestamp_bits.second.resize(9, '0'); + int64_t nanos = 0; + EXPECT_THAT(absl::SimpleAtoi(timestamp_bits.second, &nanos), IsTrue()) + << "Failed to parse time " << timestamp_bits.first; + ci_.subsecond = absl::Nanoseconds(nanos); + + absl::Span<char> view = absl::MakeSpan(buf_); + view.remove_suffix(2); + entry_.prefix_len_ = + entry_.prefix_ + ? log_internal::FormatLogPrefix( + entry_.log_severity(), entry_.timestamp(), entry_.tid(), + entry_.source_basename(), entry_.source_line(), view) + : 0; + + EXPECT_THAT(entry_.prefix_len_, Eq(view.data() - buf_.data())); + AppendTruncated(text_message, view); + view = absl::Span<char>(view.data(), view.size() + 2); + view[0] = '\n'; + view[1] = '\0'; + view.remove_prefix(2); + buf_.resize(view.data() - buf_.data()); + entry_.text_message_with_prefix_and_newline_and_nul_ = absl::MakeSpan(buf_); + } + LogEntryTestPeer(const LogEntryTestPeer&) = delete; + LogEntryTestPeer& operator=(const LogEntryTestPeer&) = delete; + + std::string FormatLogMessage() const { + return log_internal::FormatLogMessage( + entry_.log_severity(), ci_.cs, ci_.subsecond, entry_.tid(), + entry_.source_basename(), entry_.source_line(), entry_.text_message()); + } + std::string FormatPrefixIntoSizedBuffer(size_t sz) { + std::string str(sz, '\0'); + absl::Span<char> buf(&str[0], str.size()); + const size_t prefix_size = log_internal::FormatLogPrefix( + entry_.log_severity(), entry_.timestamp(), entry_.tid(), + entry_.source_basename(), entry_.source_line(), buf); + EXPECT_THAT(prefix_size, Eq(buf.data() - str.data())); + str.resize(prefix_size); + return str; + } + const absl::LogEntry& entry() const { return entry_; } + + private: + absl::LogEntry entry_; + absl::TimeZone::CivilInfo ci_; + std::vector<char> buf_; +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +namespace { +constexpr bool kUsePrefix = true, kNoPrefix = false; + +TEST(LogEntryTest, Baseline) { + LogEntryTestPeer entry("foo.cc", 1234, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.6789", 451, "hello world"); + EXPECT_THAT(entry.FormatLogMessage(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] ")); + for (size_t sz = strlen("I0102 03:04:05.678900 451 foo.cc:1234] ") + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT("I0102 03:04:05.678900 451 foo.cc:1234] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678900 451 foo.cc:1234] hello world\n")); + EXPECT_THAT(entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world")); + EXPECT_THAT(entry.entry().text_message(), Eq("hello world")); +} + +TEST(LogEntryTest, NoPrefix) { + LogEntryTestPeer entry("foo.cc", 1234, kNoPrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.6789", 451, "hello world"); + EXPECT_THAT(entry.FormatLogMessage(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world")); + // These methods are not responsible for honoring `prefix()`. + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] ")); + for (size_t sz = strlen("I0102 03:04:05.678900 451 foo.cc:1234] ") + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT("I0102 03:04:05.678900 451 foo.cc:1234] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline(), + Eq("hello world\n")); + EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("hello world\n")); + EXPECT_THAT(entry.entry().text_message_with_prefix(), Eq("hello world")); + EXPECT_THAT(entry.entry().text_message(), Eq("hello world")); +} + +TEST(LogEntryTest, EmptyFields) { + LogEntryTestPeer entry("", 0, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05", 0, ""); + const std::string format_message = entry.FormatLogMessage(); + EXPECT_THAT(format_message, Eq("I0102 03:04:05.000000 0 :0] ")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), Eq(format_message)); + for (size_t sz = format_message.size() + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT(format_message, + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.000000 0 :0] \n")); + EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.000000 0 :0] \n")); + EXPECT_THAT(entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.000000 0 :0] ")); + EXPECT_THAT(entry.entry().text_message(), Eq("")); +} + +TEST(LogEntryTest, NegativeFields) { + if (std::is_signed<absl::LogEntry::tid_t>::value) { + LogEntryTestPeer entry("foo.cc", -1234, kUsePrefix, + absl::LogSeverity::kInfo, "2020-01-02T03:04:05.6789", + -451, "hello world"); + EXPECT_THAT(entry.FormatLogMessage(), + Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] ")); + for (size_t sz = + strlen("I0102 03:04:05.678900 -451 foo.cc:-1234] ") + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT("I0102 03:04:05.678900 -451 foo.cc:-1234] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world\n")); + EXPECT_THAT(entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world")); + EXPECT_THAT(entry.entry().text_message(), Eq("hello world")); + } else { + LogEntryTestPeer entry("foo.cc", -1234, kUsePrefix, + absl::LogSeverity::kInfo, "2020-01-02T03:04:05.6789", + 451, "hello world"); + EXPECT_THAT(entry.FormatLogMessage(), + Eq("I0102 03:04:05.678900 451 foo.cc:-1234] hello world")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678900 451 foo.cc:-1234] ")); + for (size_t sz = + strlen("I0102 03:04:05.678900 451 foo.cc:-1234] ") + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT("I0102 03:04:05.678900 451 foo.cc:-1234] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678900 451 foo.cc:-1234] hello world\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678900 451 foo.cc:-1234] hello world\n")); + EXPECT_THAT(entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678900 451 foo.cc:-1234] hello world")); + EXPECT_THAT(entry.entry().text_message(), Eq("hello world")); + } +} + +TEST(LogEntryTest, LongFields) { + LogEntryTestPeer entry( + "I am the very model of a modern Major-General / " + "I've information vegetable, animal, and mineral.", + 2147483647, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.678967896789", 2147483647, + "I know the kings of England, and I quote the fights historical / " + "From Marathon to Waterloo, in order categorical."); + EXPECT_THAT(entry.FormatLogMessage(), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] ")); + for (size_t sz = + strlen("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] ") + + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT( + "I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.\n")); + EXPECT_THAT(entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.")); + EXPECT_THAT( + entry.entry().text_message(), + Eq("I know the kings of England, and I quote the fights historical / " + "From Marathon to Waterloo, in order categorical.")); +} + +TEST(LogEntryTest, LongNegativeFields) { + if (std::is_signed<absl::LogEntry::tid_t>::value) { + LogEntryTestPeer entry( + "I am the very model of a modern Major-General / " + "I've information vegetable, animal, and mineral.", + -2147483647, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.678967896789", -2147483647, + "I know the kings of England, and I quote the fights historical / " + "From Marathon to Waterloo, in order categorical."); + EXPECT_THAT( + entry.FormatLogMessage(), + Eq("I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] ")); + for (size_t sz = + strlen( + "I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] ") + + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT( + "I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.")); + EXPECT_THAT( + entry.entry().text_message(), + Eq("I know the kings of England, and I quote the fights historical / " + "From Marathon to Waterloo, in order categorical.")); + } else { + LogEntryTestPeer entry( + "I am the very model of a modern Major-General / " + "I've information vegetable, animal, and mineral.", + -2147483647, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.678967896789", 2147483647, + "I know the kings of England, and I quote the fights historical / " + "From Marathon to Waterloo, in order categorical."); + EXPECT_THAT( + entry.FormatLogMessage(), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] ")); + for (size_t sz = + strlen( + "I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] ") + + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT( + "I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.")); + EXPECT_THAT( + entry.entry().text_message(), + Eq("I know the kings of England, and I quote the fights historical / " + "From Marathon to Waterloo, in order categorical.")); + } +} + +} // namespace diff --git a/absl/log/log_format_test.cc b/absl/log/log_format_test.cc new file mode 100644 index 00000000..3fdb358a --- /dev/null +++ b/absl/log/log_format_test.cc @@ -0,0 +1,1525 @@ +// +// Copyright 2022 The Abseil 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. + +#include <math.h> + +#include <iomanip> +#include <limits> +#include <sstream> +#include <string> +#include <type_traits> + +#ifdef __ANDROID__ +#include <android/api-level.h> +#endif +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/log/internal/config.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/log/scoped_mock_log.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" + +namespace { +using ::absl::log_internal::MatchesOstream; +using ::absl::log_internal::TextMessage; +using ::absl::log_internal::TextPrefix; + +using ::testing::AllOf; +using ::testing::AnyOf; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::Truly; +using ::testing::Types; + +using ::testing::Each; +using ::testing::Ge; +using ::testing::Le; +using ::testing::SizeIs; + +// Some aspects of formatting streamed data (e.g. pointer handling) are +// implementation-defined. Others are buggy in supported implementations. +// These tests validate that the formatting matches that performed by a +// `std::ostream` and also that the result is one of a list of expected formats. + +std::ostringstream ComparisonStream() { + std::ostringstream str; + str.setf(std::ios_base::showbase | std::ios_base::boolalpha | + std::ios_base::internal); + return str; +} + +TEST(LogFormatTest, NoMessage) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int log_line = __LINE__ + 1; + auto do_log = [] { LOG(INFO); }; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(MatchesOstream(ComparisonStream())), + TextPrefix(Truly([=](absl::string_view msg) { + return absl::EndsWith( + msg, absl::StrCat(" log_format_test.cc:", log_line, "] ")); + })), + TextMessage(IsEmpty()), ENCODED_MESSAGE(EqualsProto(R"pb()pb"))))); + + test_sink.StartCapturingLogs(); + do_log(); +} + +template <typename T> +class CharLogFormatTest : public testing::Test {}; +using CharTypes = Types<char, signed char, unsigned char>; +TYPED_TEST_SUITE(CharLogFormatTest, CharTypes); + +TYPED_TEST(CharLogFormatTest, Printable) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = 'x'; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("x")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "x" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(CharLogFormatTest, Unprintable) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = 0xeeu; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("\xee")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "\xee" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +template <typename T> +class UnsignedIntLogFormatTest : public testing::Test {}; +using UnsignedIntTypes = Types<unsigned short, unsigned int, // NOLINT + unsigned long, unsigned long long>; // NOLINT +TYPED_TEST_SUITE(UnsignedIntLogFormatTest, UnsignedIntTypes); + +TYPED_TEST(UnsignedIntLogFormatTest, Positive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = 224; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("224")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "224" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(UnsignedIntLogFormatTest, BitfieldPositive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const struct { + TypeParam bits : 6; + } value{42}; + auto comparison_stream = ComparisonStream(); + comparison_stream << value.bits; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("42")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "42" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value.bits; +} + +template <typename T> +class SignedIntLogFormatTest : public testing::Test {}; +using SignedIntTypes = + Types<signed short, signed int, signed long, signed long long>; // NOLINT +TYPED_TEST_SUITE(SignedIntLogFormatTest, SignedIntTypes); + +TYPED_TEST(SignedIntLogFormatTest, Positive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = 224; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("224")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "224" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(SignedIntLogFormatTest, Negative) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = -112; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-112")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "-112" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(SignedIntLogFormatTest, BitfieldPositive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const struct { + TypeParam bits : 6; + } value{21}; + auto comparison_stream = ComparisonStream(); + comparison_stream << value.bits; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("21")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "21" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value.bits; +} + +TYPED_TEST(SignedIntLogFormatTest, BitfieldNegative) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const struct { + TypeParam bits : 6; + } value{-21}; + auto comparison_stream = ComparisonStream(); + comparison_stream << value.bits; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-21")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "-21" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value.bits; +} + +// Ignore these test cases on GCC due to "is too small to hold all values ..." +// warning. +#if !defined(__GNUC__) || defined(__clang__) +// The implementation may choose a signed or unsigned integer type to represent +// this enum, so it may be tested by either `UnsignedEnumLogFormatTest` or +// `SignedEnumLogFormatTest`. +enum MyUnsignedEnum { + MyUnsignedEnum_ZERO = 0, + MyUnsignedEnum_FORTY_TWO = 42, + MyUnsignedEnum_TWO_HUNDRED_TWENTY_FOUR = 224, +}; +enum MyUnsignedIntEnum : unsigned int { + MyUnsignedIntEnum_ZERO = 0, + MyUnsignedIntEnum_FORTY_TWO = 42, + MyUnsignedIntEnum_TWO_HUNDRED_TWENTY_FOUR = 224, +}; + +template <typename T> +class UnsignedEnumLogFormatTest : public testing::Test {}; +using UnsignedEnumTypes = std::conditional< + std::is_signed<std::underlying_type<MyUnsignedEnum>::type>::value, + Types<MyUnsignedIntEnum>, Types<MyUnsignedEnum, MyUnsignedIntEnum>>::type; +TYPED_TEST_SUITE(UnsignedEnumLogFormatTest, UnsignedEnumTypes); + +TYPED_TEST(UnsignedEnumLogFormatTest, Positive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = static_cast<TypeParam>(224); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("224")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "224" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(UnsignedEnumLogFormatTest, BitfieldPositive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const struct { + TypeParam bits : 6; + } value{static_cast<TypeParam>(42)}; + auto comparison_stream = ComparisonStream(); + comparison_stream << value.bits; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("42")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "42" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value.bits; +} + +enum MySignedEnum { + MySignedEnum_NEGATIVE_ONE_HUNDRED_TWELVE = -112, + MySignedEnum_NEGATIVE_TWENTY_ONE = -21, + MySignedEnum_ZERO = 0, + MySignedEnum_TWENTY_ONE = 21, + MySignedEnum_TWO_HUNDRED_TWENTY_FOUR = 224, +}; +enum MySignedIntEnum : signed int { + MySignedIntEnum_NEGATIVE_ONE_HUNDRED_TWELVE = -112, + MySignedIntEnum_NEGATIVE_TWENTY_ONE = -21, + MySignedIntEnum_ZERO = 0, + MySignedIntEnum_TWENTY_ONE = 21, + MySignedIntEnum_TWO_HUNDRED_TWENTY_FOUR = 224, +}; + +template <typename T> +class SignedEnumLogFormatTest : public testing::Test {}; +using SignedEnumTypes = std::conditional< + std::is_signed<std::underlying_type<MyUnsignedEnum>::type>::value, + Types<MyUnsignedEnum, MySignedEnum, MySignedIntEnum>, + Types<MySignedEnum, MySignedIntEnum>>::type; +TYPED_TEST_SUITE(SignedEnumLogFormatTest, SignedEnumTypes); + +TYPED_TEST(SignedEnumLogFormatTest, Positive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = static_cast<TypeParam>(224); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("224")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "224" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(SignedEnumLogFormatTest, Negative) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = static_cast<TypeParam>(-112); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-112")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "-112" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(SignedEnumLogFormatTest, BitfieldPositive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const struct { + TypeParam bits : 6; + } value{static_cast<TypeParam>(21)}; + auto comparison_stream = ComparisonStream(); + comparison_stream << value.bits; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("21")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "21" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value.bits; +} + +TYPED_TEST(SignedEnumLogFormatTest, BitfieldNegative) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const struct { + TypeParam bits : 6; + } value{static_cast<TypeParam>(-21)}; + auto comparison_stream = ComparisonStream(); + comparison_stream << value.bits; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-21")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "-21" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value.bits; +} +#endif + +TEST(FloatLogFormatTest, Positive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const float value = 6.02e23f; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("6.02e+23")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "6.02e+23" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(FloatLogFormatTest, Negative) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const float value = -6.02e23f; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-6.02e+23")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "-6.02e+23" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(FloatLogFormatTest, NegativeExponent) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const float value = 6.02e-23f; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("6.02e-23")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "6.02e-23" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(DoubleLogFormatTest, Positive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 6.02e23; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("6.02e+23")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "6.02e+23" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(DoubleLogFormatTest, Negative) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = -6.02e23; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-6.02e+23")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "-6.02e+23" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(DoubleLogFormatTest, NegativeExponent) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 6.02e-23; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("6.02e-23")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "6.02e-23" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +template <typename T> +class FloatingPointLogFormatTest : public testing::Test {}; +using FloatingPointTypes = Types<float, double>; +TYPED_TEST_SUITE(FloatingPointLogFormatTest, FloatingPointTypes); + +TYPED_TEST(FloatingPointLogFormatTest, Zero) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = 0.0; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("0")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "0" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(FloatingPointLogFormatTest, Integer) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = 1.0; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("1")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "1" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(FloatingPointLogFormatTest, Infinity) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = std::numeric_limits<TypeParam>::infinity(); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("inf"), Eq("Inf"))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "inf" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(FloatingPointLogFormatTest, NegativeInfinity) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = -std::numeric_limits<TypeParam>::infinity(); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("-inf"), Eq("-Inf"))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "-inf" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(FloatingPointLogFormatTest, NaN) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = std::numeric_limits<TypeParam>::quiet_NaN(); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("nan"), Eq("NaN"))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "nan" })pb"))))); + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(FloatingPointLogFormatTest, NegativeNaN) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = + std::copysign(std::numeric_limits<TypeParam>::quiet_NaN(), -1.0); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("-nan"), Eq("nan"), Eq("NaN"), Eq("-nan(ind)"))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "-nan" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +template <typename T> +class VoidPtrLogFormatTest : public testing::Test {}; +using VoidPtrTypes = Types<void *, const void *>; +TYPED_TEST_SUITE(VoidPtrLogFormatTest, VoidPtrTypes); + +TYPED_TEST(VoidPtrLogFormatTest, Null) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = nullptr; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("(nil)"), Eq("0"), Eq("0x0"), + Eq("00000000"), Eq("0000000000000000")))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(VoidPtrLogFormatTest, NonNull) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = reinterpret_cast<TypeParam>(0xdeadbeefULL); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("0xdeadbeef"), Eq("DEADBEEF"), + Eq("00000000DEADBEEF"))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "0xdeadbeef" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +template <typename T> +class VolatileVoidPtrLogFormatTest : public testing::Test {}; +using VolatileVoidPtrTypes = Types<volatile void *, const volatile void *>; +TYPED_TEST_SUITE(VolatileVoidPtrLogFormatTest, VolatileVoidPtrTypes); + +TYPED_TEST(VolatileVoidPtrLogFormatTest, Null) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = nullptr; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("false")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "false" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(VolatileVoidPtrLogFormatTest, NonNull) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = reinterpret_cast<TypeParam>(0xdeadbeefLL); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("true")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "true" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +template <typename T> +class CharPtrLogFormatTest : public testing::Test {}; +using CharPtrTypes = Types<char *, const char *>; +TYPED_TEST_SUITE(CharPtrLogFormatTest, CharPtrTypes); + +TYPED_TEST(CharPtrLogFormatTest, Null) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + // Streaming `([cv] char *)nullptr` into a `std::ostream` is UB, and some C++ + // standard library implementations choose to crash. We take measures to log + // something useful instead of crashing, even when that differs from the + // standard library in use (and thus the behavior of `std::ostream`). + const TypeParam value = nullptr; + + EXPECT_CALL( + test_sink, + Send(AllOf( + // `MatchesOstream` deliberately omitted since we deliberately differ. + TextMessage(Eq("(null)")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(null)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(CharPtrLogFormatTest, NonNull) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + char data[] = "value"; + const TypeParam value = data; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("value")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "value" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(BoolLogFormatTest, True) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const bool value = true; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("true")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "true" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(BoolLogFormatTest, False) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const bool value = false; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("false")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "false" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(LogFormatTest, StringLiteral) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + auto comparison_stream = ComparisonStream(); + comparison_stream << "value"; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("value")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + literal: "value" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << "value"; +} + +TEST(LogFormatTest, CharArray) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + char value[] = "value"; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("value")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "value" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +class CustomClass {}; +std::ostream& operator<<(std::ostream& os, const CustomClass&) { + return os << "CustomClass{}"; +} + +TEST(LogFormatTest, Custom) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + CustomClass value; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("CustomClass{}")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "CustomClass{}" + })pb"))))); + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +class CustomClassNonCopyable { + public: + CustomClassNonCopyable() = default; + CustomClassNonCopyable(const CustomClassNonCopyable&) = delete; + CustomClassNonCopyable& operator=(const CustomClassNonCopyable&) = delete; +}; +std::ostream& operator<<(std::ostream& os, const CustomClassNonCopyable&) { + return os << "CustomClassNonCopyable{}"; +} + +TEST(LogFormatTest, CustomNonCopyable) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + CustomClassNonCopyable value; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("CustomClassNonCopyable{}")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "CustomClassNonCopyable{}" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(ManipulatorLogFormatTest, BoolAlphaTrue) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const bool value = true; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::noboolalpha << value << " " // + << std::boolalpha << value << " " // + << std::noboolalpha << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("1 true 1")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "1" } + value { literal: " " } + value { str: "true" } + value { literal: " " } + value { str: "1" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::noboolalpha << value << " " // + << std::boolalpha << value << " " // + << std::noboolalpha << value; +} + +TEST(ManipulatorLogFormatTest, BoolAlphaFalse) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const bool value = false; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::noboolalpha << value << " " // + << std::boolalpha << value << " " // + << std::noboolalpha << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("0 false 0")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "0" } + value { literal: " " } + value { str: "false" } + value { literal: " " } + value { str: "0" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::noboolalpha << value << " " // + << std::boolalpha << value << " " // + << std::noboolalpha << value; +} + +TEST(ManipulatorLogFormatTest, ShowPoint) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 77.0; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::noshowpoint << value << " " // + << std::showpoint << value << " " // + << std::noshowpoint << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77 77.0000 77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" } + value { literal: " " } + value { str: "77.0000" } + value { literal: " " } + value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::noshowpoint << value << " " // + << std::showpoint << value << " " // + << std::noshowpoint << value; +} + +TEST(ManipulatorLogFormatTest, ShowPos) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::noshowpos << value << " " // + << std::showpos << value << " " // + << std::noshowpos << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77 +77 77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" } + value { literal: " " } + value { str: "+77" } + value { literal: " " } + value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::noshowpos << value << " " // + << std::showpos << value << " " // + << std::noshowpos << value; +} + +TEST(ManipulatorLogFormatTest, UppercaseFloat) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 7.7e7; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::nouppercase << value << " " // + << std::uppercase << value << " " // + << std::nouppercase << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("7.7e+07 7.7E+07 7.7e+07")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "7.7e+07" } + value { literal: " " } + value { str: "7.7E+07" } + value { literal: " " } + value { str: "7.7e+07" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::nouppercase << value << " " // + << std::uppercase << value << " " // + << std::nouppercase << value; +} + +TEST(ManipulatorLogFormatTest, Hex) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 0x77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::hex << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("0x77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "0x77" + })pb"))))); + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hex << value; +} + +TEST(ManipulatorLogFormatTest, Oct) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 077; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::oct << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("077")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "077" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::oct << value; +} + +TEST(ManipulatorLogFormatTest, Dec) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::hex << std::dec << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hex << std::dec << value; +} + +TEST(ManipulatorLogFormatTest, ShowbaseHex) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 0x77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::hex // + << std::noshowbase << value << " " // + << std::showbase << value << " " // + << std::noshowbase << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77 0x77 77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" } + value { literal: " " } + value { str: "0x77" } + value { literal: " " } + value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hex // + << std::noshowbase << value << " " // + << std::showbase << value << " " // + << std::noshowbase << value; +} + +TEST(ManipulatorLogFormatTest, ShowbaseOct) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 077; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::oct // + << std::noshowbase << value << " " // + << std::showbase << value << " " // + << std::noshowbase << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77 077 77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" } + value { literal: " " } + value { str: "077" } + value { literal: " " } + value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::oct // + << std::noshowbase << value << " " // + << std::showbase << value << " " // + << std::noshowbase << value; +} + +TEST(ManipulatorLogFormatTest, UppercaseHex) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 0xbeef; + auto comparison_stream = ComparisonStream(); + comparison_stream // + << std::hex // + << std::nouppercase << value << " " // + << std::uppercase << value << " " // + << std::nouppercase << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("0xbeef 0XBEEF 0xbeef")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "0xbeef" } + value { literal: " " } + value { str: "0XBEEF" } + value { literal: " " } + value { str: "0xbeef" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hex // + << std::nouppercase << value << " " // + << std::uppercase << value << " " // + << std::nouppercase << value; +} + +TEST(ManipulatorLogFormatTest, FixedFloat) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 7.7e7; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::fixed << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77000000.000000")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "77000000.000000" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::fixed << value; +} + +TEST(ManipulatorLogFormatTest, ScientificFloat) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 7.7e7; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::scientific << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("7.700000e+07")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "7.700000e+07" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::scientific << value; +} + +#if defined(__BIONIC__) && (!defined(__ANDROID_API__) || __ANDROID_API__ < 22) +// Bionic doesn't support `%a` until API 22, so this prints 'a' even if the +// C++ standard library implements it correctly (by forwarding to printf). +#elif defined(__GLIBCXX__) && __cplusplus < 201402L +// libstdc++ shipped C++11 support without `std::hexfloat`. +#else +TEST(ManipulatorLogFormatTest, FixedAndScientificFloat) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 7.7e7; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::setiosflags(std::ios_base::scientific | + std::ios_base::fixed) + << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("0x1.25bb50p+26"), Eq("0x1.25bb5p+26"), + Eq("0x1.25bb500000000p+26"))), + ENCODED_MESSAGE(EqualsProto(R"pb( + value { str: "0x1.25bb5p+26" })pb"))))); + + test_sink.StartCapturingLogs(); + + // This combination should mean the same thing as `std::hexfloat`. + LOG(INFO) << std::setiosflags(std::ios_base::scientific | + std::ios_base::fixed) + << value; +} +#endif + +#if defined(__BIONIC__) && (!defined(__ANDROID_API__) || __ANDROID_API__ < 22) +// Bionic doesn't support `%a` until API 22, so this prints 'a' even if the C++ +// standard library supports `std::hexfloat` (by forwarding to printf). +#elif defined(__GLIBCXX__) && __cplusplus < 201402L +// libstdc++ shipped C++11 support without `std::hexfloat`. +#else +TEST(ManipulatorLogFormatTest, HexfloatFloat) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 7.7e7; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::hexfloat << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("0x1.25bb50p+26"), Eq("0x1.25bb5p+26"), + Eq("0x1.25bb500000000p+26"))), + ENCODED_MESSAGE(EqualsProto(R"pb( + value { str: "0x1.25bb5p+26" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hexfloat << value; +} +#endif + +TEST(ManipulatorLogFormatTest, DefaultFloatFloat) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 7.7e7; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::hexfloat << std::defaultfloat << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("7.7e+07")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "7.7e+07" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hexfloat << std::defaultfloat << value; +} + +TEST(ManipulatorLogFormatTest, Ends) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + auto comparison_stream = ComparisonStream(); + comparison_stream << std::ends; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq(absl::string_view("\0", 1))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "\0" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::ends; +} + +TEST(ManipulatorLogFormatTest, Endl) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + auto comparison_stream = ComparisonStream(); + comparison_stream << std::endl; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("\n"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::endl; +} + +TEST(ManipulatorLogFormatTest, SetIosFlags) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 0x77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::resetiosflags(std::ios_base::basefield) + << std::setiosflags(std::ios_base::hex) << value << " " // + << std::resetiosflags(std::ios_base::basefield) + << std::setiosflags(std::ios_base::dec) << value; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("0x77 119")), + // `std::setiosflags` and `std::resetiosflags` aren't manipulators. + // We're unable to distinguish their return type(s) from arbitrary + // user-defined types and thus don't suppress the empty str value. + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "0x77" } + value { literal: " " } + value { str: "119" } + )pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::resetiosflags(std::ios_base::basefield) + << std::setiosflags(std::ios_base::hex) << value << " " // + << std::resetiosflags(std::ios_base::basefield) + << std::setiosflags(std::ios_base::dec) << value; +} + +TEST(ManipulatorLogFormatTest, SetBase) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 0x77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::setbase(16) << value << " " // + << std::setbase(0) << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("0x77 119")), + // `std::setbase` isn't a manipulator. We're unable to + // distinguish its return type from arbitrary user-defined + // types and thus don't suppress the empty str value. + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "0x77" } + value { literal: " " } + value { str: "119" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::setbase(16) << value << " " // + << std::setbase(0) << value; +} + +TEST(ManipulatorLogFormatTest, SetPrecision) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 6.022140857e23; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::setprecision(4) << value; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("6.022e+23")), + // `std::setprecision` isn't a manipulator. We're unable to + // distinguish its return type from arbitrary user-defined + // types and thus don't suppress the empty str value. + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "6.022e+23" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::setprecision(4) << value; +} + +TEST(ManipulatorLogFormatTest, SetPrecisionOverflow) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 6.022140857e23; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::setprecision(200) << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("602214085700000015187968")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "602214085700000015187968" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::setprecision(200) << value; +} + +TEST(ManipulatorLogFormatTest, SetW) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::setw(8) << value; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq(" 77")), + // `std::setw` isn't a manipulator. We're unable to + // distinguish its return type from arbitrary user-defined + // types and thus don't suppress the empty str value. + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: " 77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::setw(8) << value; +} + +TEST(ManipulatorLogFormatTest, Left) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = -77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::left << std::setw(8) << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-77 ")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "-77 " + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::left << std::setw(8) << value; +} + +TEST(ManipulatorLogFormatTest, Right) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = -77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::right << std::setw(8) << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq(" -77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: " -77" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::right << std::setw(8) << value; +} + +TEST(ManipulatorLogFormatTest, Internal) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = -77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::internal << std::setw(8) << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("- 77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "- 77" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::internal << std::setw(8) << value; +} + +TEST(ManipulatorLogFormatTest, SetFill) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::setfill('0') << std::setw(8) << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("00000077")), + // `std::setfill` isn't a manipulator. We're + // unable to distinguish its return + // type from arbitrary user-defined types and + // thus don't suppress the empty str value. + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "00000077" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::setfill('0') << std::setw(8) << value; +} + +class FromCustomClass {}; +std::ostream& operator<<(std::ostream& os, const FromCustomClass&) { + return os << "FromCustomClass{}" << std::hex; +} + +TEST(ManipulatorLogFormatTest, FromCustom) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + FromCustomClass value; + auto comparison_stream = ComparisonStream(); + comparison_stream << value << " " << 0x77; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("FromCustomClass{} 0x77")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "FromCustomClass{}" } + value { literal: " " } + value { str: "0x77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value << " " << 0x77; +} + +class StreamsNothing {}; +std::ostream& operator<<(std::ostream& os, const StreamsNothing&) { return os; } + +TEST(ManipulatorLogFormatTest, CustomClassStreamsNothing) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + StreamsNothing value; + auto comparison_stream = ComparisonStream(); + comparison_stream << value << 77; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value << 77; +} + +// Tests that verify the behavior when more data are streamed into a `LOG` +// statement than fit in the buffer. +// Structured logging scenario is tested in other unit tests since the output is +// significantly different. +TEST(OverflowTest, TruncatesStrings) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + // This message is too long and should be truncated to some unspecified size + // no greater than the buffer size but not too much less either. It should be + // truncated rather than discarded. + constexpr size_t buffer_size = 15000; + + EXPECT_CALL(test_sink, + Send(TextMessage( + AllOf(SizeIs(AllOf(Ge(buffer_size - 256), Le(buffer_size))), + Each(Eq('x')))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::string(2 * buffer_size, 'x'); +} + +} // namespace diff --git a/absl/log/log_macro_hygiene_test.cc b/absl/log/log_macro_hygiene_test.cc new file mode 100644 index 00000000..ab6461f5 --- /dev/null +++ b/absl/log/log_macro_hygiene_test.cc @@ -0,0 +1,171 @@ +// +// Copyright 2022 The Abseil 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. + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/log_severity.h" +#include "absl/log/log.h" +#include "absl/log/scoped_mock_log.h" + +namespace { +using ::testing::_; +using ::testing::Eq; + +namespace not_absl { + +class Dummy { + public: + Dummy() {} + + private: + Dummy(const Dummy&) = delete; + Dummy& operator=(const Dummy&) = delete; +}; + +// This line tests that local definitions of INFO, WARNING, ERROR, and +// etc don't shadow the global ones used by the logging macros. If +// they do, the LOG() calls in the tests won't compile, catching the +// bug. +const Dummy INFO, WARNING, ERROR, FATAL, NUM_SEVERITIES; + +// These makes sure that the uses of same-named types in the +// implementation of the logging macros are fully qualified. +class string {}; +class vector {}; +class LogMessage {}; +class LogMessageFatal {}; +class LogMessageQuietlyFatal {}; +class LogMessageVoidify {}; +class LogSink {}; +class NullStream {}; +class NullStreamFatal {}; + +} // namespace not_absl + +using namespace not_absl; // NOLINT + +// Tests for LOG(LEVEL(()). + +TEST(LogHygieneTest, WorksForQualifiedSeverity) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + ::testing::InSequence seq; + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "To INFO")); + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kWarning, _, "To WARNING")); + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kError, _, "To ERROR")); + + test_sink.StartCapturingLogs(); + // Note that LOG(LEVEL()) expects the severity as a run-time + // expression (as opposed to a compile-time constant). Hence we + // test that :: is allowed before INFO, etc. + LOG(LEVEL(absl::LogSeverity::kInfo)) << "To INFO"; + LOG(LEVEL(absl::LogSeverity::kWarning)) << "To WARNING"; + LOG(LEVEL(absl::LogSeverity::kError)) << "To ERROR"; +} + +TEST(LogHygieneTest, WorksWithAlternativeINFOSymbol) { + const double INFO ABSL_ATTRIBUTE_UNUSED = 7.77; + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "Hello world")); + + test_sink.StartCapturingLogs(); + LOG(INFO) << "Hello world"; +} + +TEST(LogHygieneTest, WorksWithAlternativeWARNINGSymbol) { + const double WARNING ABSL_ATTRIBUTE_UNUSED = 7.77; + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kWarning, _, "Hello world")); + + test_sink.StartCapturingLogs(); + LOG(WARNING) << "Hello world"; +} + +TEST(LogHygieneTest, WorksWithAlternativeERRORSymbol) { + const double ERROR ABSL_ATTRIBUTE_UNUSED = 7.77; + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kError, _, "Hello world")); + + test_sink.StartCapturingLogs(); + LOG(ERROR) << "Hello world"; +} + +TEST(LogHygieneTest, WorksWithAlternativeLEVELSymbol) { + const double LEVEL ABSL_ATTRIBUTE_UNUSED = 7.77; + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kError, _, "Hello world")); + + test_sink.StartCapturingLogs(); + LOG(LEVEL(absl::LogSeverity::kError)) << "Hello world"; +} + +#define INFO Bogus +#ifdef NDEBUG +constexpr bool IsOptimized = false; +#else +constexpr bool IsOptimized = true; +#endif + +TEST(LogHygieneTest, WorksWithINFODefined) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "Hello world")) + .Times(2 + (IsOptimized ? 2 : 0)); + + test_sink.StartCapturingLogs(); + LOG(INFO) << "Hello world"; + LOG_IF(INFO, true) << "Hello world"; + + DLOG(INFO) << "Hello world"; + DLOG_IF(INFO, true) << "Hello world"; +} + +#undef INFO + +TEST(LogHygieneTest, ExpressionEvaluationInLEVELSeverity) { + auto i = static_cast<int>(absl::LogSeverity::kInfo); + LOG(LEVEL(++i)) << "hello world"; // NOLINT + EXPECT_THAT(i, Eq(static_cast<int>(absl::LogSeverity::kInfo) + 1)); +} + +TEST(LogHygieneTest, ExpressionEvaluationInStreamedMessage) { + int i = 0; + LOG(INFO) << ++i; + EXPECT_THAT(i, 1); + LOG_IF(INFO, false) << ++i; + EXPECT_THAT(i, 1); +} + +// Tests that macros are usable in unbraced switch statements. +// ----------------------------------------------------------- + +class UnbracedSwitchCompileTest { + static void Log() { + switch (0) { + case 0: + LOG(INFO); + break; + default: + break; + } + } +}; + +} // namespace diff --git a/absl/log/log_modifier_methods_test.cc b/absl/log/log_modifier_methods_test.cc new file mode 100644 index 00000000..a9bf38b7 --- /dev/null +++ b/absl/log/log_modifier_methods_test.cc @@ -0,0 +1,231 @@ +// +// Copyright 2022 The Abseil 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. + +#include <errno.h> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/log/internal/test_actions.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/log/log_sink.h" +#include "absl/log/scoped_mock_log.h" +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" + +namespace { +#if GTEST_HAS_DEATH_TEST +using ::absl::log_internal::DeathTestExpectedLogging; +using ::absl::log_internal::DeathTestUnexpectedLogging; +using ::absl::log_internal::DeathTestValidateExpectations; +using ::absl::log_internal::DiedOfQFatal; +#endif +using ::absl::log_internal::LogSeverity; +using ::absl::log_internal::Prefix; +using ::absl::log_internal::SourceBasename; +using ::absl::log_internal::SourceFilename; +using ::absl::log_internal::SourceLine; +using ::absl::log_internal::Stacktrace; +using ::absl::log_internal::TextMessage; +using ::absl::log_internal::TextMessageWithPrefix; +using ::absl::log_internal::TextMessageWithPrefixAndNewline; +using ::absl::log_internal::TextPrefix; +using ::absl::log_internal::ThreadID; +using ::absl::log_internal::Timestamp; +using ::absl::log_internal::Verbosity; + +using ::testing::AllOf; +using ::testing::AnyNumber; +using ::testing::AnyOf; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::IsFalse; +using ::testing::Truly; + +TEST(TailCallsModifiesTest, AtLocationFileLine) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf( + // The metadata should change: + SourceFilename(Eq("/my/very/very/very_long_source_file.cc")), + SourceBasename(Eq("very_long_source_file.cc")), SourceLine(Eq(777)), + // The logged line should change too, even though the prefix must + // grow to fit the new metadata. + TextMessageWithPrefix(Truly([](absl::string_view msg) { + return absl::EndsWith(msg, + " very_long_source_file.cc:777] hello world"); + }))))); + + test_sink.StartCapturingLogs(); + LOG(INFO).AtLocation("/my/very/very/very_long_source_file.cc", 777) + << "hello world"; +} + +TEST(TailCallsModifiesTest, NoPrefix) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(AllOf(Prefix(IsFalse()), TextPrefix(IsEmpty()), + TextMessageWithPrefix(Eq("hello world"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO).NoPrefix() << "hello world"; +} + +TEST(TailCallsModifiesTest, NoPrefixNoMessageNoShirtNoShoesNoService) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, + Send(AllOf(Prefix(IsFalse()), TextPrefix(IsEmpty()), + TextMessageWithPrefix(IsEmpty()), + TextMessageWithPrefixAndNewline(Eq("\n"))))); + test_sink.StartCapturingLogs(); + LOG(INFO).NoPrefix(); +} + +TEST(TailCallsModifiesTest, WithVerbosity) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(Verbosity(Eq(2)))); + + test_sink.StartCapturingLogs(); + LOG(INFO).WithVerbosity(2) << "hello world"; +} + +TEST(TailCallsModifiesTest, WithVerbosityNoVerbosity) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, + Send(Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)))); + + test_sink.StartCapturingLogs(); + LOG(INFO).WithVerbosity(2).WithVerbosity(absl::LogEntry::kNoVerbosityLevel) + << "hello world"; +} + +TEST(TailCallsModifiesTest, WithTimestamp) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(Timestamp(Eq(absl::UnixEpoch())))); + + test_sink.StartCapturingLogs(); + LOG(INFO).WithTimestamp(absl::UnixEpoch()) << "hello world"; +} + +TEST(TailCallsModifiesTest, WithThreadID) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(AllOf(ThreadID(Eq(1234))))); + + test_sink.StartCapturingLogs(); + LOG(INFO).WithThreadID(1234) << "hello world"; +} + +TEST(TailCallsModifiesTest, WithMetadataFrom) { + class ForwardingLogSink : public absl::LogSink { + public: + void Send(const absl::LogEntry &entry) override { + LOG(LEVEL(entry.log_severity())).WithMetadataFrom(entry) + << "forwarded: " << entry.text_message(); + } + } forwarding_sink; + + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("fake/file")), SourceBasename(Eq("file")), + SourceLine(Eq(123)), Prefix(IsFalse()), + LogSeverity(Eq(absl::LogSeverity::kWarning)), + Timestamp(Eq(absl::UnixEpoch())), ThreadID(Eq(456)), + TextMessage(Eq("forwarded: hello world")), Verbosity(Eq(7)), + ENCODED_MESSAGE( + EqualsProto(R"pb(value { literal: "forwarded: " } + value { str: "hello world" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(WARNING) + .AtLocation("fake/file", 123) + .NoPrefix() + .WithTimestamp(absl::UnixEpoch()) + .WithThreadID(456) + .WithVerbosity(7) + .ToSinkOnly(&forwarding_sink) + << "hello world"; +} + +TEST(TailCallsModifiesTest, WithPerror) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(AnyOf(Eq("hello world: Bad file number [9]"), + Eq("hello world: Bad file descriptor [9]"), + Eq("hello world: Bad file descriptor [8]"))), + ENCODED_MESSAGE( + AnyOf(EqualsProto(R"pb(value { literal: "hello world" } + value { literal: ": " } + value { str: "Bad file number" } + value { literal: " [" } + value { str: "9" } + value { literal: "]" })pb"), + EqualsProto(R"pb(value { literal: "hello world" } + value { literal: ": " } + value { str: "Bad file descriptor" } + value { literal: " [" } + value { str: "9" } + value { literal: "]" })pb"), + EqualsProto(R"pb(value { literal: "hello world" } + value { literal: ": " } + value { str: "Bad file descriptor" } + value { literal: " [" } + value { str: "8" } + value { literal: "]" })pb")))))); + + test_sink.StartCapturingLogs(); + errno = EBADF; + LOG(INFO).WithPerror() << "hello world"; +} + +#if GTEST_HAS_DEATH_TEST +TEST(ModifierMethodDeathTest, ToSinkOnlyQFatal) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink( + absl::MockLogDefault::kDisallowUnexpected); + + auto do_log = [&test_sink] { + LOG(QFATAL).ToSinkOnly(&test_sink.UseAsLocalSink()) << "hello world"; + }; + + EXPECT_CALL(test_sink, Send) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("hello world")), + Stacktrace(IsEmpty())))) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + do_log(); + }, + DiedOfQFatal, DeathTestValidateExpectations()); +} +#endif + +} // namespace diff --git a/absl/log/log_sink.cc b/absl/log/log_sink.cc new file mode 100644 index 00000000..01d7ca82 --- /dev/null +++ b/absl/log/log_sink.cc @@ -0,0 +1,23 @@ +// Copyright 2022 The Abseil 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. + +#include "absl/log/log_sink.h" + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +void LogSink::KeyFunction() const {} +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/log_sink.h b/absl/log/log_sink.h new file mode 100644 index 00000000..9bfa6f86 --- /dev/null +++ b/absl/log/log_sink.h @@ -0,0 +1,64 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/log_sink.h +// ----------------------------------------------------------------------------- +// +// This header declares the interface class `absl::LogSink`. + +#ifndef ABSL_LOG_LOG_SINK_H_ +#define ABSL_LOG_LOG_SINK_H_ + +#include "absl/base/config.h" +#include "absl/log/log_entry.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// absl::LogSink +// +// `absl::LogSink` is an interface which can be extended to intercept and +// process particular messages (with `LOG.ToSinkOnly()` or +// `LOG.ToSinkAlso()`) or all messages (if registered with +// `absl::AddLogSink`). Implementations must be thread-safe, and should take +// care not to take any locks that might be held by the `LOG` caller. +class LogSink { + public: + virtual ~LogSink() = default; + + // LogSink::Send() + // + // `Send` is called synchronously during the log statement. + // + // It is safe to use `LOG` within an implementation of `Send`. `ToSinkOnly` + // and `ToSinkAlso` are safe in general but can be used to create an infinite + // loop if you try. + virtual void Send(const absl::LogEntry& entry) = 0; + + // LogSink::Flush() + // + // Sinks that buffer messages should override this method to flush the buffer + // and return. + virtual void Flush() {} + + private: + // https://lld.llvm.org/missingkeyfunction.html#missing-key-function + virtual void KeyFunction() const final; // NOLINT(readability/inheritance) +}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_LOG_SINK_H_ diff --git a/absl/log/log_sink_registry.h b/absl/log/log_sink_registry.h new file mode 100644 index 00000000..bf76ccee --- /dev/null +++ b/absl/log/log_sink_registry.h @@ -0,0 +1,61 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/log_sink_registry.h +// ----------------------------------------------------------------------------- +// +// This header declares APIs to operate on global set of registered log sinks. + +#ifndef ABSL_LOG_LOG_SINK_REGISTRY_H_ +#define ABSL_LOG_LOG_SINK_REGISTRY_H_ + +#include "absl/base/config.h" +#include "absl/log/internal/log_sink_set.h" +#include "absl/log/log_sink.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// AddLogSink(), RemoveLogSink() +// +// Adds or removes a `absl::LogSink` as a consumer of logging data. +// +// These functions are thread-safe. +// +// It is an error to attempt to add a sink that's already registered or to +// attempt to remove one that isn't. +// +// To avoid unbounded recursion, dispatch to registered `absl::LogSink`s is +// disabled per-thread while running the `Send()` method of registered +// `absl::LogSink`s. Affected messages are dispatched to a special internal +// sink instead which writes them to `stderr`. +// +// Do not call these inside `absl::LogSink::Send`. +inline void AddLogSink(absl::LogSink* sink) { log_internal::AddLogSink(sink); } +inline void RemoveLogSink(absl::LogSink* sink) { + log_internal::RemoveLogSink(sink); +} + +// FlushLogSinks() +// +// Calls `absl::LogSink::Flush` on all registered sinks. +// +// Do not call this inside `absl::LogSink::Send`. +inline void FlushLogSinks() { log_internal::FlushLogSinks(); } + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_LOG_SINK_REGISTRY_H_ diff --git a/absl/log/log_sink_test.cc b/absl/log/log_sink_test.cc new file mode 100644 index 00000000..8903da72 --- /dev/null +++ b/absl/log/log_sink_test.cc @@ -0,0 +1,419 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/log_sink.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/log/internal/test_actions.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/log/log_sink_registry.h" +#include "absl/log/scoped_mock_log.h" +#include "absl/strings/string_view.h" + +namespace { + +using ::absl::log_internal::DeathTestExpectedLogging; +using ::absl::log_internal::DeathTestUnexpectedLogging; +using ::absl::log_internal::DeathTestValidateExpectations; +using ::absl::log_internal::DiedOfFatal; +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::HasSubstr; +using ::testing::InSequence; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +// Tests for global log sink registration. +// --------------------------------------- + +TEST(LogSinkRegistryTest, AddLogSink) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + InSequence s; + EXPECT_CALL(test_sink, Log(_, _, "hello world")).Times(0); + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, __FILE__, "Test : 42")); + EXPECT_CALL(test_sink, + Log(absl::LogSeverity::kWarning, __FILE__, "Danger ahead")); + EXPECT_CALL(test_sink, + Log(absl::LogSeverity::kError, __FILE__, "This is an error")); + + LOG(INFO) << "hello world"; + test_sink.StartCapturingLogs(); + + LOG(INFO) << "Test : " << 42; + LOG(WARNING) << "Danger" << ' ' << "ahead"; + LOG(ERROR) << "This is an error"; + + test_sink.StopCapturingLogs(); + LOG(INFO) << "Goodby world"; +} + +TEST(LogSinkRegistryTest, MultipleLogSinks) { + absl::ScopedMockLog test_sink1(absl::MockLogDefault::kDisallowUnexpected); + absl::ScopedMockLog test_sink2(absl::MockLogDefault::kDisallowUnexpected); + + ::testing::InSequence seq; + EXPECT_CALL(test_sink1, Log(absl::LogSeverity::kInfo, _, "First")).Times(1); + EXPECT_CALL(test_sink2, Log(absl::LogSeverity::kInfo, _, "First")).Times(0); + + EXPECT_CALL(test_sink1, Log(absl::LogSeverity::kInfo, _, "Second")).Times(1); + EXPECT_CALL(test_sink2, Log(absl::LogSeverity::kInfo, _, "Second")).Times(1); + + EXPECT_CALL(test_sink1, Log(absl::LogSeverity::kInfo, _, "Third")).Times(0); + EXPECT_CALL(test_sink2, Log(absl::LogSeverity::kInfo, _, "Third")).Times(1); + + LOG(INFO) << "Before first"; + + test_sink1.StartCapturingLogs(); + LOG(INFO) << "First"; + + test_sink2.StartCapturingLogs(); + LOG(INFO) << "Second"; + + test_sink1.StopCapturingLogs(); + LOG(INFO) << "Third"; + + test_sink2.StopCapturingLogs(); + LOG(INFO) << "Fourth"; +} + +TEST(LogSinkRegistrationDeathTest, DuplicateSinkRegistration) { + ASSERT_DEATH_IF_SUPPORTED( + { + absl::ScopedMockLog sink; + sink.StartCapturingLogs(); + absl::AddLogSink(&sink.UseAsLocalSink()); + }, + HasSubstr("Duplicate log sinks")); +} + +TEST(LogSinkRegistrationDeathTest, MismatchSinkRemoval) { + ASSERT_DEATH_IF_SUPPORTED( + { + absl::ScopedMockLog sink; + absl::RemoveLogSink(&sink.UseAsLocalSink()); + }, + HasSubstr("Mismatched log sink")); +} + +// Tests for log sink semantic. +// --------------------------------------- + +TEST(LogSinkTest, FlushSinks) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Flush()).Times(2); + + test_sink.StartCapturingLogs(); + + absl::FlushLogSinks(); + absl::FlushLogSinks(); +} + +TEST(LogSinkDeathTest, DeathInSend) { + class FatalSendSink : public absl::LogSink { + public: + void Send(const absl::LogEntry&) override { LOG(FATAL) << "goodbye world"; } + }; + + FatalSendSink sink; + EXPECT_EXIT({ LOG(INFO).ToSinkAlso(&sink) << "hello world"; }, DiedOfFatal, + _); +} + +// Tests for explicit log sink redirection. +// --------------------------------------- + +TEST(LogSinkTest, ToSinkAlso) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + absl::ScopedMockLog another_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Log(_, _, "hello world")); + EXPECT_CALL(another_sink, Log(_, _, "hello world")); + + test_sink.StartCapturingLogs(); + LOG(INFO).ToSinkAlso(&another_sink.UseAsLocalSink()) << "hello world"; +} + +TEST(LogSinkTest, ToSinkOnly) { + absl::ScopedMockLog another_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(another_sink, Log(_, _, "hello world")); + LOG(INFO).ToSinkOnly(&another_sink.UseAsLocalSink()) << "hello world"; +} + +TEST(LogSinkTest, ToManySinks) { + absl::ScopedMockLog sink1(absl::MockLogDefault::kDisallowUnexpected); + absl::ScopedMockLog sink2(absl::MockLogDefault::kDisallowUnexpected); + absl::ScopedMockLog sink3(absl::MockLogDefault::kDisallowUnexpected); + absl::ScopedMockLog sink4(absl::MockLogDefault::kDisallowUnexpected); + absl::ScopedMockLog sink5(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(sink3, Log(_, _, "hello world")); + EXPECT_CALL(sink4, Log(_, _, "hello world")); + EXPECT_CALL(sink5, Log(_, _, "hello world")); + + LOG(INFO) + .ToSinkAlso(&sink1.UseAsLocalSink()) + .ToSinkAlso(&sink2.UseAsLocalSink()) + .ToSinkOnly(&sink3.UseAsLocalSink()) + .ToSinkAlso(&sink4.UseAsLocalSink()) + .ToSinkAlso(&sink5.UseAsLocalSink()) + << "hello world"; +} + +class ReentrancyTest : public ::testing::Test { + protected: + ReentrancyTest() = default; + enum class LogMode : int { kNormal, kToSinkAlso, kToSinkOnly }; + + class ReentrantSendLogSink : public absl::LogSink { + public: + explicit ReentrantSendLogSink(absl::LogSeverity severity, + absl::LogSink* sink, LogMode mode) + : severity_(severity), sink_(sink), mode_(mode) {} + explicit ReentrantSendLogSink(absl::LogSeverity severity) + : ReentrantSendLogSink(severity, nullptr, LogMode::kNormal) {} + + void Send(const absl::LogEntry&) override { + switch (mode_) { + case LogMode::kNormal: + LOG(LEVEL(severity_)) << "The log is coming from *inside the sink*."; + break; + case LogMode::kToSinkAlso: + LOG(LEVEL(severity_)).ToSinkAlso(sink_) + << "The log is coming from *inside the sink*."; + break; + case LogMode::kToSinkOnly: + LOG(LEVEL(severity_)).ToSinkOnly(sink_) + << "The log is coming from *inside the sink*."; + break; + default: + ABSL_RAW_LOG(FATAL, "Invalid mode %d.\n", static_cast<int>(mode_)); + } + } + + private: + absl::LogSeverity severity_; + absl::LogSink* sink_; + LogMode mode_; + }; + + static absl::string_view LogAndReturn(absl::LogSeverity severity, + absl::string_view to_log, + absl::string_view to_return) { + LOG(LEVEL(severity)) << to_log; + return to_return; + } +}; + +TEST_F(ReentrancyTest, LogFunctionThatLogs) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + InSequence seq; + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "hello")); + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "world")); + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kWarning, _, "danger")); + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "here")); + + test_sink.StartCapturingLogs(); + LOG(INFO) << LogAndReturn(absl::LogSeverity::kInfo, "hello", "world"); + LOG(INFO) << LogAndReturn(absl::LogSeverity::kWarning, "danger", "here"); +} + +TEST_F(ReentrancyTest, RegisteredLogSinkThatLogsInSend) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + ReentrantSendLogSink renentrant_sink(absl::LogSeverity::kInfo); + EXPECT_CALL(test_sink, Log(_, _, "hello world")); + + test_sink.StartCapturingLogs(); + absl::AddLogSink(&renentrant_sink); + LOG(INFO) << "hello world"; + absl::RemoveLogSink(&renentrant_sink); +} + +TEST_F(ReentrancyTest, AlsoLogSinkThatLogsInSend) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kInfo); + EXPECT_CALL(test_sink, Log(_, _, "hello world")); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")); + + test_sink.StartCapturingLogs(); + LOG(INFO).ToSinkAlso(&reentrant_sink) << "hello world"; +} + +TEST_F(ReentrancyTest, RegisteredAlsoLogSinkThatLogsInSend) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kInfo); + EXPECT_CALL(test_sink, Log(_, _, "hello world")); + // We only call into the test_log sink once with this message, since the + // second time log statement is run we are in "ThreadIsLogging" mode and all + // the log statements are redirected into stderr. + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")); + + test_sink.StartCapturingLogs(); + absl::AddLogSink(&reentrant_sink); + LOG(INFO).ToSinkAlso(&reentrant_sink) << "hello world"; + absl::RemoveLogSink(&reentrant_sink); +} + +TEST_F(ReentrancyTest, OnlyLogSinkThatLogsInSend) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kInfo); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")); + + test_sink.StartCapturingLogs(); + LOG(INFO).ToSinkOnly(&reentrant_sink) << "hello world"; +} + +TEST_F(ReentrancyTest, RegisteredOnlyLogSinkThatLogsInSend) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kInfo); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")); + + test_sink.StartCapturingLogs(); + absl::AddLogSink(&reentrant_sink); + LOG(INFO).ToSinkOnly(&reentrant_sink) << "hello world"; + absl::RemoveLogSink(&reentrant_sink); +} + +using ReentrancyDeathTest = ReentrancyTest; + +TEST_F(ReentrancyDeathTest, LogFunctionThatLogsFatal) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + EXPECT_CALL(test_sink, Log(_, _, "hello")) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + LOG(INFO) << LogAndReturn(absl::LogSeverity::kFatal, "hello", "world"); + }, + DiedOfFatal, DeathTestValidateExpectations()); +} + +TEST_F(ReentrancyDeathTest, RegisteredLogSinkThatLogsFatalInSend) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal); + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + EXPECT_CALL(test_sink, Log(_, _, "hello world")) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + absl::AddLogSink(&reentrant_sink); + LOG(INFO) << "hello world"; + // No need to call RemoveLogSink - process is dead at this point. + }, + DiedOfFatal, DeathTestValidateExpectations()); +} + +TEST_F(ReentrancyDeathTest, AlsoLogSinkThatLogsFatalInSend) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal); + + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + EXPECT_CALL(test_sink, Log(_, _, "hello world")) + .WillOnce(DeathTestExpectedLogging()); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + LOG(INFO).ToSinkAlso(&reentrant_sink) << "hello world"; + }, + DiedOfFatal, DeathTestValidateExpectations()); +} + +TEST_F(ReentrancyDeathTest, RegisteredAlsoLogSinkThatLogsFatalInSend) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal); + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + EXPECT_CALL(test_sink, Log(_, _, "hello world")) + .WillOnce(DeathTestExpectedLogging()); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + absl::AddLogSink(&reentrant_sink); + LOG(INFO).ToSinkAlso(&reentrant_sink) << "hello world"; + // No need to call RemoveLogSink - process is dead at this point. + }, + DiedOfFatal, DeathTestValidateExpectations()); +} + +TEST_F(ReentrancyDeathTest, OnlyLogSinkThatLogsFatalInSend) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal); + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + LOG(INFO).ToSinkOnly(&reentrant_sink) << "hello world"; + }, + DiedOfFatal, DeathTestValidateExpectations()); +} + +TEST_F(ReentrancyDeathTest, RegisteredOnlyLogSinkThatLogsFatalInSend) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal); + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + absl::AddLogSink(&reentrant_sink); + LOG(INFO).ToSinkOnly(&reentrant_sink) << "hello world"; + // No need to call RemoveLogSink - process is dead at this point. + }, + DiedOfFatal, DeathTestValidateExpectations()); +} + +} // namespace diff --git a/absl/log/log_streamer.h b/absl/log/log_streamer.h new file mode 100644 index 00000000..c3776cec --- /dev/null +++ b/absl/log/log_streamer.h @@ -0,0 +1,176 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/log_streamer.h +// ----------------------------------------------------------------------------- +// +// This header declares the class `LogStreamer` and convenience functions to +// construct LogStreamer objects with different associated log severity levels. + +#ifndef ABSL_LOG_LOG_STREAMER_H_ +#define ABSL_LOG_LOG_STREAMER_H_ + +#include <ios> +#include <memory> +#include <ostream> +#include <string> +#include <utility> + +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/log.h" +#include "absl/memory/memory.h" +#include "absl/strings/internal/ostringstream.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// LogStreamer +// +// Although you can stream into `LOG(INFO)`, you can't pass it into a function +// that takes a `std::ostream` parameter. `LogStreamer::stream()` provides a +// `std::ostream` that buffers everything that's streamed in. The buffer's +// contents are logged as if by `LOG` when the `LogStreamer` is destroyed. +// If nothing is streamed in, an empty message is logged. If the specified +// severity is `absl::LogSeverity::kFatal`, the program will be terminated when +// the `LogStreamer` is destroyed regardless of whether any data were streamed +// in. +// +// Factory functions corresponding to the `absl::LogSeverity` enumerators +// are provided for convenience; if the desired severity is variable, invoke the +// constructor directly. +// +// LogStreamer is movable, but not copyable. +// +// Examples: +// +// ShaveYakAndWriteToStream( +// yak, absl::LogInfoStreamer(__FILE__, __LINE__).stream()); +// +// { +// // This logs a single line containing data streamed by all three function +// // calls. +// absl::LogStreamer streamer(absl::LogSeverity::kInfo, __FILE__, __LINE__); +// ShaveYakAndWriteToStream(yak1, streamer.stream()); +// streamer.stream() << " "; +// ShaveYakAndWriteToStream(yak2, streamer.stream()); +// streamer.stream() << " "; +// ShaveYakAndWriteToStreamPointer(yak3, &streamer.stream()); +// } +class LogStreamer final { + public: + // LogStreamer::LogStreamer() + // + // Creates a LogStreamer with a given `severity` that will log a message + // attributed to the given `file` and `line`. + explicit LogStreamer(absl::LogSeverity severity, absl::string_view file, + int line) + : severity_(severity), + line_(line), + file_(file), + stream_( + absl::make_unique<absl::strings_internal::OStringStream>(&buf_)) { + // To match `LOG`'s defaults: + stream_->setf(std::ios_base::showbase | std::ios_base::boolalpha); + } + + // A moved-from `absl::LogStreamer` does not `LOG` when destroyed, + // and a program that streams into one has undefined behavior. + LogStreamer(LogStreamer&& that) noexcept + : severity_(that.severity_), + line_(that.line_), + file_(std::move(that.file_)), + buf_(std::move(that.buf_)), + stream_(that.stream_ + ? absl::make_unique<absl::strings_internal::OStringStream>( + &buf_) + : nullptr) { + that.stream_.reset(); + } + LogStreamer& operator=(LogStreamer&& that) { + LOG_IF(LEVEL(severity_), stream_).AtLocation(file_, line_) << buf_; + severity_ = that.severity_; + file_ = std::move(that.file_); + line_ = that.line_; + buf_ = std::move(that.buf_); + stream_ = + that.stream_ + ? absl::make_unique<absl::strings_internal::OStringStream>(&buf_) + : nullptr; + that.stream_.reset(); + return *this; + } + + // LogStreamer::~LogStreamer() + // + // Logs this LogStreamer's buffered content as if by LOG. + ~LogStreamer() { + LOG_IF(LEVEL(severity_), stream_).AtLocation(file_, line_) << buf_; + } + + // LogStreamer::stream() + // + // Returns the `std::ostream` to use to write into this LogStreamer' internal + // buffer. + std::ostream& stream() { return *stream_; } + + private: + absl::LogSeverity severity_; + int line_; + std::string file_; + std::string buf_; + // TODO(durandal): de-pointerize this once we are off of our old mostly-C++11 + // libstdc++ (which lacks move constructors for std streams). + // `stream_` is null in a moved-from `LogStreamer`; this is in fact how we + // recognize one to avoid logging when it is destroyed or reassigned. + std::unique_ptr<absl::strings_internal::OStringStream> stream_; +}; + +// LogInfoStreamer() +// +// Returns a LogStreamer that writes at level LogSeverity::kInfo. +inline LogStreamer LogInfoStreamer(absl::string_view file, int line) { + return absl::LogStreamer(absl::LogSeverity::kInfo, file, line); +} + +// LogWarningStreamer() +// +// Returns a LogStreamer that writes at level LogSeverity::kWarning. +inline LogStreamer LogWarningStreamer(absl::string_view file, int line) { + return absl::LogStreamer(absl::LogSeverity::kWarning, file, line); +} + +// LogErrorStreamer() +// +// Returns a LogStreamer that writes at level LogSeverity::kError. +inline LogStreamer LogErrorStreamer(absl::string_view file, int line) { + return absl::LogStreamer(absl::LogSeverity::kError, file, line); +} + +// LogFatalStreamer() +// +// Returns a LogStreamer that writes at level LogSeverity::kFatal. +// +// The program will be terminated when this `LogStreamer` is destroyed, +// regardless of whether any data were streamed in. +inline LogStreamer LogFatalStreamer(absl::string_view file, int line) { + return absl::LogStreamer(absl::LogSeverity::kFatal, file, line); +} + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_LOG_STREAMER_H_ diff --git a/absl/log/log_streamer_test.cc b/absl/log/log_streamer_test.cc new file mode 100644 index 00000000..d7beb5bc --- /dev/null +++ b/absl/log/log_streamer_test.cc @@ -0,0 +1,361 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/log_streamer.h" + +#include <iostream> +#include <utility> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/internal/sysinfo.h" +#include "absl/base/log_severity.h" +#include "absl/log/internal/test_actions.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/log/scoped_mock_log.h" +#include "absl/strings/string_view.h" + +namespace { +using ::absl::log_internal::DeathTestExpectedLogging; +using ::absl::log_internal::DeathTestUnexpectedLogging; +using ::absl::log_internal::DeathTestValidateExpectations; +#if GTEST_HAS_DEATH_TEST +using ::absl::log_internal::DiedOfFatal; +#endif +using ::absl::log_internal::LogSeverity; +using ::absl::log_internal::Prefix; +using ::absl::log_internal::SourceFilename; +using ::absl::log_internal::SourceLine; +using ::absl::log_internal::Stacktrace; +using ::absl::log_internal::TextMessage; +using ::absl::log_internal::ThreadID; +using ::absl::log_internal::TimestampInMatchWindow; +using ::testing::AnyNumber; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::IsEmpty; +using ::testing::IsTrue; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +void WriteToStream(absl::string_view data, std::ostream* os) { + *os << "WriteToStream: " << data; +} +void WriteToStreamRef(absl::string_view data, std::ostream& os) { + os << "WriteToStreamRef: " << data; +} + +TEST(LogStreamerTest, LogInfoStreamer) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kInfo)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("WriteToStream: foo")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "WriteToStream: foo" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + WriteToStream("foo", &absl::LogInfoStreamer("path/file.cc", 1234).stream()); +} + +TEST(LogStreamerTest, LogWarningStreamer) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kWarning)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("WriteToStream: foo")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "WriteToStream: foo" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + WriteToStream("foo", + &absl::LogWarningStreamer("path/file.cc", 1234).stream()); +} + +TEST(LogStreamerTest, LogErrorStreamer) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kError)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("WriteToStream: foo")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "WriteToStream: foo" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + WriteToStream("foo", &absl::LogErrorStreamer("path/file.cc", 1234).stream()); +} + +#if GTEST_HAS_DEATH_TEST +TEST(LogStreamerDeathTest, LogFatalStreamer) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + + EXPECT_CALL(test_sink, Send) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + EXPECT_CALL( + test_sink, + Send(AllOf( + SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("WriteToStream: foo")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "WriteToStream: foo" + })pb"))))) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + WriteToStream("foo", + &absl::LogFatalStreamer("path/file.cc", 1234).stream()); + }, + DiedOfFatal, DeathTestValidateExpectations()); +} +#endif + +TEST(LogStreamerTest, LogStreamer) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kError)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("WriteToStream: foo")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "WriteToStream: foo" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + WriteToStream( + "foo", &absl::LogStreamer(absl::LogSeverity::kError, "path/file.cc", 1234) + .stream()); +} + +#if GTEST_HAS_DEATH_TEST +TEST(LogStreamerDeathTest, LogStreamer) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + + EXPECT_CALL(test_sink, Send) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + EXPECT_CALL( + test_sink, + Send(AllOf( + SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("WriteToStream: foo")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "WriteToStream: foo" + })pb"))))) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + WriteToStream("foo", &absl::LogStreamer(absl::LogSeverity::kFatal, + "path/file.cc", 1234) + .stream()); + }, + DiedOfFatal, DeathTestValidateExpectations()); +} +#endif + +TEST(LogStreamerTest, PassedByReference) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + TextMessage(Eq("WriteToStreamRef: foo")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "WriteToStreamRef: foo" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + WriteToStreamRef("foo", absl::LogInfoStreamer("path/file.cc", 1234).stream()); +} + +TEST(LogStreamerTest, StoredAsLocal) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + auto streamer = absl::LogInfoStreamer("path/file.cc", 1234); + WriteToStream("foo", &streamer.stream()); + streamer.stream() << " "; + WriteToStreamRef("bar", streamer.stream()); + + // The call should happen when `streamer` goes out of scope; if it + // happened before this `EXPECT_CALL` the call would be unexpected and the + // test would fail. + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + TextMessage(Eq("WriteToStream: foo WriteToStreamRef: bar")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { + str: "WriteToStream: foo WriteToStreamRef: bar" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); +} + +#if GTEST_HAS_DEATH_TEST +TEST(LogStreamerDeathTest, StoredAsLocal) { + EXPECT_EXIT( + { + // This is fatal when it goes out of scope, but not until then: + auto streamer = absl::LogFatalStreamer("path/file.cc", 1234); + std::cerr << "I'm still alive" << std::endl; + WriteToStream("foo", &streamer.stream()); + }, + DiedOfFatal, HasSubstr("I'm still alive")); +} +#endif + +TEST(LogStreamerTest, LogsEmptyLine) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(AllOf(SourceFilename(Eq("path/file.cc")), + SourceLine(Eq(1234)), TextMessage(Eq("")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + absl::LogInfoStreamer("path/file.cc", 1234); +} + +#if GTEST_HAS_DEATH_TEST +TEST(LogStreamerDeathTest, LogsEmptyLine) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + EXPECT_CALL( + test_sink, + Send(AllOf( + SourceFilename(Eq("path/file.cc")), TextMessage(Eq("")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "" })pb"))))) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + // This is fatal even though it's never used: + auto streamer = absl::LogFatalStreamer("path/file.cc", 1234); + }, + DiedOfFatal, DeathTestValidateExpectations()); +} +#endif + +TEST(LogStreamerTest, MoveConstruction) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf( + SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + LogSeverity(Eq(absl::LogSeverity::kInfo)), + TextMessage(Eq("hello world")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "hello world" })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + auto streamer1 = absl::LogInfoStreamer("path/file.cc", 1234); + streamer1.stream() << "hello"; + absl::LogStreamer streamer2(std::move(streamer1)); + streamer2.stream() << " world"; +} + +TEST(LogStreamerTest, MoveAssignment) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + LogSeverity(Eq(absl::LogSeverity::kInfo)), + TextMessage(Eq("hello")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "hello" })pb")), + Stacktrace(IsEmpty())))); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("elsewhere/name.cc")), SourceLine(Eq(5678)), + LogSeverity(Eq(absl::LogSeverity::kWarning)), + TextMessage(Eq("world; goodbye")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "world; goodbye" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + auto streamer1 = absl::LogInfoStreamer("path/file.cc", 1234); + streamer1.stream() << "hello"; + auto streamer2 = absl::LogWarningStreamer("elsewhere/name.cc", 5678); + streamer2.stream() << "world"; + streamer1 = std::move(streamer2); + streamer1.stream() << "; goodbye"; +} + +TEST(LogStreamerTest, CorrectDefaultFlags) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + // The `boolalpha` and `showbase` flags should be set by default, to match + // `LOG`. + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("false0xdeadbeef"))))) + .Times(2); + + test_sink.StartCapturingLogs(); + absl::LogInfoStreamer("path/file.cc", 1234).stream() + << false << std::hex << 0xdeadbeef; + LOG(INFO) << false << std::hex << 0xdeadbeef; +} + +} // namespace diff --git a/absl/log/scoped_mock_log.cc b/absl/log/scoped_mock_log.cc new file mode 100644 index 00000000..4ebc0a9f --- /dev/null +++ b/absl/log/scoped_mock_log.cc @@ -0,0 +1,86 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/scoped_mock_log.h" + +#include <atomic> +#include <string> + +#include "gmock/gmock.h" +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" +#include "absl/log/log_sink_registry.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +ScopedMockLog::ScopedMockLog(MockLogDefault default_exp) + : sink_(this), is_capturing_logs_(false) { + if (default_exp == MockLogDefault::kIgnoreUnexpected) { + // Ignore all calls to Log we did not set expectations for. + EXPECT_CALL(*this, Log).Times(::testing::AnyNumber()); + } else { + // Disallow all calls to Log we did not set expectations for. + EXPECT_CALL(*this, Log).Times(0); + } + // By default Send mock forwards to Log mock. + EXPECT_CALL(*this, Send) + .Times(::testing::AnyNumber()) + .WillRepeatedly([this](const absl::LogEntry& entry) { + is_triggered_.store(true, std::memory_order_relaxed); + Log(entry.log_severity(), std::string(entry.source_filename()), + std::string(entry.text_message())); + }); + + // By default We ignore all Flush calls. + EXPECT_CALL(*this, Flush).Times(::testing::AnyNumber()); +} + +ScopedMockLog::~ScopedMockLog() { + ABSL_RAW_CHECK(is_triggered_.load(std::memory_order_relaxed), + "Did you forget to call StartCapturingLogs()?"); + + if (is_capturing_logs_) StopCapturingLogs(); +} + +void ScopedMockLog::StartCapturingLogs() { + ABSL_RAW_CHECK(!is_capturing_logs_, + "StartCapturingLogs() can be called only when the " + "absl::ScopedMockLog object is not capturing logs."); + + is_capturing_logs_ = true; + is_triggered_.store(true, std::memory_order_relaxed); + absl::AddLogSink(&sink_); +} + +void ScopedMockLog::StopCapturingLogs() { + ABSL_RAW_CHECK(is_capturing_logs_, + "StopCapturingLogs() can be called only when the " + "absl::ScopedMockLog object is capturing logs."); + + is_capturing_logs_ = false; + absl::RemoveLogSink(&sink_); +} + +absl::LogSink& ScopedMockLog::UseAsLocalSink() { + is_triggered_.store(true, std::memory_order_relaxed); + return sink_; +} + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/scoped_mock_log.h b/absl/log/scoped_mock_log.h new file mode 100644 index 00000000..44470c16 --- /dev/null +++ b/absl/log/scoped_mock_log.h @@ -0,0 +1,194 @@ +// Copyright 2022 The Abseil 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. +// +// ----------------------------------------------------------------------------- +// File: log/scoped_mock_log.h +// ----------------------------------------------------------------------------- +// +// This header declares `class absl::ScopedMockLog`, for use in testing. + +#ifndef ABSL_LOG_SCOPED_MOCK_LOG_H_ +#define ABSL_LOG_SCOPED_MOCK_LOG_H_ + +#include <atomic> +#include <string> + +#include "gmock/gmock.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// MockLogDefault +// +// Controls how ScopedMockLog responds to unexpected calls by default. +enum class MockLogDefault { kIgnoreUnexpected, kDisallowUnexpected }; + +// ScopedMockLog +// +// ScopedMockLog is a LogSink that intercepts LOG() messages issued during its +// lifespan. +// +// Using this together with GoogleTest, it's easy to test how a piece of code +// calls LOG(). The typical usage, noting the distinction between +// "uninteresting" and "unexpected", looks like this: +// +// using ::testing::_; +// using ::testing::AnyNumber; +// using ::testing::EndsWith; +// using ::testing::kDoNotCaptureLogsYet; +// using ::testing::Lt; +// +// TEST(FooTest, LogsCorrectly) { +// // Simple robust setup, ignores unexpected logs. +// absl::ScopedMockLog log; +// +// // We expect the WARNING "Something bad!" exactly twice. +// EXPECT_CALL(log, Log(absl::LogSeverity::kWarning, _, "Something bad!")) +// .Times(2); +// +// // But we want no messages from foo.cc. +// EXPECT_CALL(log, Log(_, EndsWith("/foo.cc"), _)).Times(0); +// +// log.StartCapturingLogs(); // Call this after done setting expectations. +// Foo(); // Exercises the code under test. +// } +// +// TEST(BarTest, LogsExactlyCorrectly) { +// // Strict checking, fails for unexpected logs. +// absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); +// +// // ... but ignore low severity messages +// EXPECT_CALL(log, Log(Lt(absl::LogSeverity::kWarning), _, _)) +// .Times(AnyNumber()); +// +// // We expect the ERROR "Something bad!" exactly once. +// EXPECT_CALL(log, Log(absl::LogSeverity::kError, EndsWith("/foo.cc"), +// "Something bad!")) +// .Times(1); +// +// log.StartCapturingLogs(); // Call this after done setting expectations. +// Bar(); // Exercises the code under test. +// } +// +// Note that in a multi-threaded environment, all LOG() messages from a single +// thread will be handled in sequence, but that cannot be guaranteed for +// messages from different threads. In fact, if the same or multiple +// expectations are matched on two threads concurrently, their actions will be +// executed concurrently as well and may interleave. +class ScopedMockLog final { + public: + // ScopedMockLog::ScopedMockLog() + // + // Sets up the log and adds default expectations. + explicit ScopedMockLog( + MockLogDefault default_exp = MockLogDefault::kIgnoreUnexpected); + ScopedMockLog(const ScopedMockLog&) = delete; + ScopedMockLog& operator=(const ScopedMockLog&) = delete; + + // ScopedMockLog::~ScopedMockLog() + // + // Stops intercepting logs and destroys this ScopedMockLog. + ~ScopedMockLog(); + + // ScopedMockLog::StartCapturingLogs() + // + // Starts log capturing if the object isn't already doing so. Otherwise + // crashes. + // + // Usually this method is called in the same thread that created this + // ScopedMockLog. It is the user's responsibility to not call this method if + // another thread may be calling it or StopCapturingLogs() at the same time. + // It is undefined behavior to add expectations while capturing logs is + // enabled. + void StartCapturingLogs(); + + // ScopedMockLog::StopCapturingLogs() + // + // Stops log capturing if the object is capturing logs. Otherwise crashes. + // + // Usually this method is called in the same thread that created this object. + // It is the user's responsibility to not call this method if another thread + // may be calling it or StartCapturingLogs() at the same time. + // + // It is UB to add expectations, while capturing logs is enabled. + void StopCapturingLogs(); + + // ScopedMockLog::UseAsLocalSink() + // + // Each `ScopedMockLog` is implemented with an `absl::LogSink`; this method + // returns a reference to that sink (e.g. for use with + // `LOG(...).ToSinkOnly()`) and marks the `ScopedMockLog` as having been used + // even if `StartCapturingLogs` is never called. + absl::LogSink& UseAsLocalSink(); + + // Implements the mock method: + // + // void Log(LogSeverity severity, absl::string_view file_path, + // absl::string_view message); + // + // The second argument to Log() is the full path of the source file in + // which the LOG() was issued. + // + // This is a shorthand form, which should be used by most users. Use the + // `Send` mock only if you want to add expectations for other log message + // attributes. + MOCK_METHOD(void, Log, + (absl::LogSeverity severity, const std::string& file_path, + const std::string& message)); + + // Implements the mock method: + // + // void Send(const absl::LogEntry& entry); + // + // This is the most generic form of mock that can be specified. Use this mock + // only if you want to add expectations for log message attributes different + // from the log message text, log message path and log message severity. + // + // If no expectations are specified for this mock, the default action is to + // forward the call to the `Log` mock. + MOCK_METHOD(void, Send, (const absl::LogEntry&)); + + // Implements the mock method: + // + // void Flush(); + // + // Use this mock only if you want to add expectations for log flush calls. + MOCK_METHOD(void, Flush, ()); + + private: + class ForwardingSink final : public absl::LogSink { + public: + explicit ForwardingSink(ScopedMockLog* sml) : sml_(sml) {} + ForwardingSink(const ForwardingSink&) = delete; + ForwardingSink& operator=(const ForwardingSink&) = delete; + void Send(const absl::LogEntry& entry) override { sml_->Send(entry); } + void Flush() override { sml_->Flush(); } + + private: + ScopedMockLog* sml_; + }; + + ForwardingSink sink_; + bool is_capturing_logs_; + std::atomic<bool> is_triggered_; +}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_SCOPED_MOCK_LOG_H_ diff --git a/absl/log/scoped_mock_log_test.cc b/absl/log/scoped_mock_log_test.cc new file mode 100644 index 00000000..50689a0e --- /dev/null +++ b/absl/log/scoped_mock_log_test.cc @@ -0,0 +1,290 @@ +// +// Copyright 2022 The Abseil 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. + +#include "absl/log/scoped_mock_log.h" + +#include <memory> +#include <thread> // NOLINT(build/c++11) + +#include "gmock/gmock.h" +#include "gtest/gtest-spi.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/log_severity.h" +#include "absl/log/globals.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/memory/memory.h" +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/barrier.h" +#include "absl/synchronization/notification.h" + +namespace { + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::InSequence; +using ::testing::Lt; +using ::testing::Truly; +using absl::log_internal::SourceBasename; +using absl::log_internal::SourceFilename; +using absl::log_internal::SourceLine; +using absl::log_internal::TextMessageWithPrefix; +using absl::log_internal::ThreadID; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +#if GTEST_HAS_DEATH_TEST +TEST(ScopedMockLogDeathTest, + StartCapturingLogsCannotBeCalledWhenAlreadyCapturing) { + EXPECT_DEATH( + { + absl::ScopedMockLog log; + log.StartCapturingLogs(); + log.StartCapturingLogs(); + }, + "StartCapturingLogs"); +} + +TEST(ScopedMockLogDeathTest, StopCapturingLogsCannotBeCalledWhenNotCapturing) { + EXPECT_DEATH( + { + absl::ScopedMockLog log; + log.StopCapturingLogs(); + }, + "StopCapturingLogs"); +} +#endif + +// Tests that ScopedMockLog intercepts LOG()s when it's alive. +TEST(ScopedMockLogTest, LogMockCatchAndMatchStrictExpectations) { + absl::ScopedMockLog log; + + // The following expectations must match in the order they appear. + InSequence s; + EXPECT_CALL(log, + Log(absl::LogSeverity::kWarning, HasSubstr(__FILE__), "Danger.")); + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "Working...")).Times(2); + EXPECT_CALL(log, Log(absl::LogSeverity::kError, _, "Bad!!")); + + log.StartCapturingLogs(); + LOG(WARNING) << "Danger."; + LOG(INFO) << "Working..."; + LOG(INFO) << "Working..."; + LOG(ERROR) << "Bad!!"; +} + +TEST(ScopedMockLogTest, LogMockCatchAndMatchSendExpectations) { + absl::ScopedMockLog log; + + EXPECT_CALL( + log, + Send(AllOf(SourceFilename(Eq("/my/very/very/very_long_source_file.cc")), + SourceBasename(Eq("very_long_source_file.cc")), + SourceLine(Eq(777)), ThreadID(Eq(1234)), + TextMessageWithPrefix(Truly([](absl::string_view msg) { + return absl::EndsWith( + msg, " very_long_source_file.cc:777] Info message"); + }))))); + + log.StartCapturingLogs(); + LOG(INFO) + .AtLocation("/my/very/very/very_long_source_file.cc", 777) + .WithThreadID(1234) + << "Info message"; +} + +TEST(ScopedMockLogTest, ScopedMockLogCanBeNice) { + absl::ScopedMockLog log; + + InSequence s; + EXPECT_CALL(log, + Log(absl::LogSeverity::kWarning, HasSubstr(__FILE__), "Danger.")); + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "Working...")).Times(2); + EXPECT_CALL(log, Log(absl::LogSeverity::kError, _, "Bad!!")); + + log.StartCapturingLogs(); + + // Any number of these are OK. + LOG(INFO) << "Info message."; + // Any number of these are OK. + LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger "; + + LOG(WARNING) << "Danger."; + + // Any number of these are OK. + LOG(INFO) << "Info message."; + // Any number of these are OK. + LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger "; + + LOG(INFO) << "Working..."; + + // Any number of these are OK. + LOG(INFO) << "Info message."; + // Any number of these are OK. + LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger "; + + LOG(INFO) << "Working..."; + + // Any number of these are OK. + LOG(INFO) << "Info message."; + // Any number of these are OK. + LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger "; + + LOG(ERROR) << "Bad!!"; + + // Any number of these are OK. + LOG(INFO) << "Info message."; + // Any number of these are OK. + LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger "; +} + +// Tests that ScopedMockLog generates a test failure if a message is logged +// that is not expected (here, that means ERROR or FATAL). +TEST(ScopedMockLogTest, RejectsUnexpectedLogs) { + EXPECT_NONFATAL_FAILURE( + { + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + // Any INFO and WARNING messages are permitted. + EXPECT_CALL(log, Log(Lt(absl::LogSeverity::kError), _, _)) + .Times(AnyNumber()); + log.StartCapturingLogs(); + LOG(INFO) << "Ignored"; + LOG(WARNING) << "Ignored"; + LOG(ERROR) << "Should not be ignored"; + }, + "Should not be ignored"); +} + +TEST(ScopedMockLogTest, CapturesLogsAfterStartCapturingLogs) { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfinity); + absl::ScopedMockLog log; + + // The ScopedMockLog object shouldn't see these LOGs, as it hasn't + // started capturing LOGs yet. + LOG(INFO) << "Ignored info"; + LOG(WARNING) << "Ignored warning"; + LOG(ERROR) << "Ignored error"; + + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "Expected info")); + log.StartCapturingLogs(); + + // Only this LOG will be seen by the ScopedMockLog. + LOG(INFO) << "Expected info"; +} + +TEST(ScopedMockLogTest, DoesNotCaptureLogsAfterStopCapturingLogs) { + absl::ScopedMockLog log; + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "Expected info")); + + log.StartCapturingLogs(); + + // This LOG should be seen by the ScopedMockLog. + LOG(INFO) << "Expected info"; + + log.StopCapturingLogs(); + + // The ScopedMockLog object shouldn't see these LOGs, as it has + // stopped capturing LOGs. + LOG(INFO) << "Ignored info"; + LOG(WARNING) << "Ignored warning"; + LOG(ERROR) << "Ignored error"; +} + +// Tests that all messages are intercepted regardless of issuing thread. The +// purpose of this test is NOT to exercise thread-safety. +TEST(ScopedMockLogTest, LogFromMultipleThreads) { + absl::ScopedMockLog log; + + // We don't establish an order to expectations here, since the threads may + // execute their log statements in different order. + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, __FILE__, "Thread 1")); + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, __FILE__, "Thread 2")); + + log.StartCapturingLogs(); + + absl::Barrier barrier(2); + std::thread thread1([&barrier]() { + barrier.Block(); + LOG(INFO) << "Thread 1"; + }); + std::thread thread2([&barrier]() { + barrier.Block(); + LOG(INFO) << "Thread 2"; + }); + + thread1.join(); + thread2.join(); +} + +// Tests that no sequence will be imposed on two LOG message expectations from +// different threads. This test would actually deadlock if replaced to two LOG +// statements from the same thread. +TEST(ScopedMockLogTest, NoSequenceWithMultipleThreads) { + absl::ScopedMockLog log; + + absl::Barrier barrier(2); + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, _)) + .Times(2) + .WillRepeatedly([&barrier]() { barrier.Block(); }); + + log.StartCapturingLogs(); + + std::thread thread1([]() { LOG(INFO) << "Thread 1"; }); + std::thread thread2([]() { LOG(INFO) << "Thread 2"; }); + + thread1.join(); + thread2.join(); +} + +TEST(ScopedMockLogTsanTest, + ScopedMockLogCanBeDeletedWhenAnotherThreadIsLogging) { + auto log = absl::make_unique<absl::ScopedMockLog>(); + EXPECT_CALL(*log, Log(absl::LogSeverity::kInfo, __FILE__, "Thread log")) + .Times(AnyNumber()); + + log->StartCapturingLogs(); + + absl::Notification logging_started; + + std::thread thread([&logging_started]() { + for (int i = 0; i < 100; ++i) { + if (i == 50) logging_started.Notify(); + LOG(INFO) << "Thread log"; + } + }); + + logging_started.WaitForNotification(); + log.reset(); + thread.join(); +} + +TEST(ScopedMockLogTest, AsLocalSink) { + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(log, Log(_, _, "two")); + EXPECT_CALL(log, Log(_, _, "three")); + + LOG(INFO) << "one"; + LOG(INFO).ToSinkOnly(&log.UseAsLocalSink()) << "two"; + LOG(INFO).ToSinkAlso(&log.UseAsLocalSink()) << "three"; +} + +} // namespace diff --git a/absl/log/stripping_test.cc b/absl/log/stripping_test.cc new file mode 100644 index 00000000..1672e3af --- /dev/null +++ b/absl/log/stripping_test.cc @@ -0,0 +1,339 @@ +// +// Copyright 2022 The Abseil 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. +// +// Tests for stripping of literal strings. +// --------------------------------------- +// +// When a `LOG` statement can be trivially proved at compile time to never fire, +// e.g. due to `ABSL_MIN_LOG_LEVEL`, `NDEBUG`, or some explicit condition, data +// streamed in can be dropped from the compiled program completely if they are +// not used elsewhere. This most commonly affects string literals, which users +// often want to strip to reduce binary size and/or redact information about +// their program's internals (e.g. in a release build). +// +// These tests log strings and then validate whether they appear in the compiled +// binary. This is done by opening the file corresponding to the running test +// and running a simple string search on its contents. The strings to be logged +// and searched for must be unique, and we must take care not to emit them into +// the binary in any other place, e.g. when searching for them. The latter is +// accomplished by computing them using base64; the source string appears in the +// binary but the target string is computed at runtime. + +#include <stdio.h> + +#if defined(__MACH__) +#include <mach-o/dyld.h> +#elif defined(_WIN32) +#include <Windows.h> +#include <tchar.h> +#endif + +#include <algorithm> +#include <functional> +#include <memory> +#include <ostream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/internal/strerror.h" +#include "absl/flags/internal/program_name.h" +#include "absl/log/check.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/log.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" + +namespace { +using ::testing::_; +using ::testing::Eq; +using ::testing::NotNull; + +using absl::log_internal::kAbslMinLogLevel; + +std::string Base64UnescapeOrDie(absl::string_view data) { + std::string decoded; + CHECK(absl::Base64Unescape(data, &decoded)); + return decoded; +} + +// ----------------------------------------------------------------------------- +// A Googletest matcher which searches the running binary for a given string +// ----------------------------------------------------------------------------- + +// This matcher is used to validate that literal strings streamed into +// `LOG` statements that ought to be compiled out (e.g. `LOG_IF(INFO, false)`) +// do not appear in the binary. +// +// Note that passing the string to be sought directly to `FileHasSubstr()` all +// but forces its inclusion in the binary regardless of the logging library's +// behavior. For example: +// +// LOG_IF(INFO, false) << "you're the man now dog"; +// // This will always pass: +// // EXPECT_THAT(fp, FileHasSubstr("you're the man now dog")); +// // So use this instead: +// EXPECT_THAT(fp, FileHasSubstr( +// Base64UnescapeOrDie("eW91J3JlIHRoZSBtYW4gbm93IGRvZw=="))); + +class FileHasSubstrMatcher final : public ::testing::MatcherInterface<FILE*> { + public: + explicit FileHasSubstrMatcher(absl::string_view needle) : needle_(needle) {} + + bool MatchAndExplain( + FILE* fp, ::testing::MatchResultListener* listener) const override { + std::string buf( + std::max<std::string::size_type>(needle_.size() * 2, 163840000), '\0'); + size_t buf_start_offset = 0; // The file offset of the byte at `buf[0]`. + size_t buf_data_size = 0; // The number of bytes of `buf` which contain + // data. + + ::fseek(fp, 0, SEEK_SET); + while (true) { + // Fill the buffer to capacity or EOF: + while (buf_data_size < buf.size()) { + const size_t ret = fread(&buf[buf_data_size], sizeof(char), + buf.size() - buf_data_size, fp); + if (ret == 0) break; + buf_data_size += ret; + } + if (ferror(fp)) { + *listener << "error reading file"; + return false; + } + const absl::string_view haystack(&buf[0], buf_data_size); + const auto off = haystack.find(needle_); + if (off != haystack.npos) { + *listener << "string found at offset " << buf_start_offset + off; + return true; + } + if (feof(fp)) { + *listener << "string not found"; + return false; + } + // Copy the end of `buf` to the beginning so we catch matches that span + // buffer boundaries. `buf` and `buf_data_size` are always large enough + // that these ranges don't overlap. + memcpy(&buf[0], &buf[buf_data_size - needle_.size()], needle_.size()); + buf_start_offset += buf_data_size - needle_.size(); + buf_data_size = needle_.size(); + } + } + void DescribeTo(std::ostream* os) const override { + *os << "contains the string \"" << needle_ << "\" (base64(\"" + << Base64UnescapeOrDie(needle_) << "\"))"; + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not "; + DescribeTo(os); + } + + private: + std::string needle_; +}; + +class StrippingTest : public ::testing::Test { + protected: + void SetUp() override { +#ifndef NDEBUG + // Non-optimized builds don't necessarily eliminate dead code at all, so we + // don't attempt to validate stripping against such builds. + GTEST_SKIP() << "StrippingTests skipped since this build is not optimized"; +#elif defined(__EMSCRIPTEN__) + // These tests require a way to examine the running binary and look for + // strings; there's no portable way to do that. + GTEST_SKIP() + << "StrippingTests skipped since this platform is not optimized"; +#endif + } + + // Opens this program's executable file. Returns `nullptr` and writes to + // `stderr` on failure. + std::unique_ptr<FILE, std::function<void(FILE*)>> OpenTestExecutable() { +#if defined(__linux__) + std::unique_ptr<FILE, std::function<void(FILE*)>> fp( + fopen("/proc/self/exe", "rb"), [](FILE* fp) { fclose(fp); }); + if (!fp) { + const std::string err = absl::base_internal::StrError(errno); + absl::FPrintF(stderr, "Failed to open /proc/self/exe: %s\n", err); + } + return fp; +#elif defined(__Fuchsia__) + // TODO(b/242579714): We need to restore the test coverage on this platform. + std::unique_ptr<FILE, std::function<void(FILE*)>> fp( + fopen(absl::StrCat("/pkg/bin/", + absl::flags_internal::ShortProgramInvocationName()) + .c_str(), + "rb"), + [](FILE* fp) { fclose(fp); }); + if (!fp) { + const std::string err = absl::base_internal::StrError(errno); + absl::FPrintF(stderr, "Failed to open /pkg/bin/<binary name>: %s\n", err); + } + return fp; +#elif defined(__MACH__) + uint32_t size = 0; + int ret = _NSGetExecutablePath(nullptr, &size); + if (ret != -1) { + absl::FPrintF(stderr, + "Failed to get executable path: " + "_NSGetExecutablePath(nullptr) returned %d\n", + ret); + return nullptr; + } + std::string path(size, '\0'); + ret = _NSGetExecutablePath(&path[0], &size); + if (ret != 0) { + absl::FPrintF( + stderr, + "Failed to get executable path: _NSGetExecutablePath(buffer) " + "returned %d\n", + ret); + return nullptr; + } + std::unique_ptr<FILE, std::function<void(FILE*)>> fp( + fopen(path.c_str(), "rb"), [](FILE* fp) { fclose(fp); }); + if (!fp) { + const std::string err = absl::base_internal::StrError(errno); + absl::FPrintF(stderr, "Failed to open executable at %s: %s\n", path, err); + } + return fp; +#elif defined(_WIN32) + std::basic_string<TCHAR> path(4096, _T('\0')); + while (true) { + const uint32_t ret = ::GetModuleFileName(nullptr, &path[0], path.size()); + if (ret == 0) { + absl::FPrintF( + stderr, + "Failed to get executable path: GetModuleFileName(buffer) " + "returned 0\n"); + return nullptr; + } + if (ret < path.size()) break; + path.resize(path.size() * 2, _T('\0')); + } + std::unique_ptr<FILE, std::function<void(FILE*)>> fp( + _tfopen(path.c_str(), "rb"), [](FILE* fp) { fclose(fp); }); + if (!fp) absl::FPrintF(stderr, "Failed to open executable\n"); + return fp; +#else + absl::FPrintF(stderr, + "OpenTestExecutable() unimplemented on this platform\n"); + return nullptr; +#endif + } + + ::testing::Matcher<FILE*> FileHasSubstr(absl::string_view needle) { + return MakeMatcher(new FileHasSubstrMatcher(needle)); + } +}; + +// This tests whether out methodology for testing stripping works on this +// platform by looking for one string that definitely ought to be there and one +// that definitely ought not to. If this fails, none of the `StrippingTest`s +// are going to produce meaningful results. +TEST_F(StrippingTest, Control) { + constexpr char kEncodedPositiveControl[] = + "U3RyaXBwaW5nVGVzdC5Qb3NpdGl2ZUNvbnRyb2w="; + const std::string encoded_negative_control = + absl::Base64Escape("StrippingTest.NegativeControl"); + + // Verify this mainly so we can encode other strings and know definitely they + // won't encode to `kEncodedPositiveControl`. + EXPECT_THAT(Base64UnescapeOrDie("U3RyaXBwaW5nVGVzdC5Qb3NpdGl2ZUNvbnRyb2w="), + Eq("StrippingTest.PositiveControl")); + + auto exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + EXPECT_THAT(exe.get(), FileHasSubstr(kEncodedPositiveControl)); + EXPECT_THAT(exe.get(), Not(FileHasSubstr(encoded_negative_control))); +} + +TEST_F(StrippingTest, Literal) { + // We need to load a copy of the needle string into memory (so we can search + // for it) without leaving it lying around in plaintext in the executable file + // as would happen if we used a literal. We might (or might not) leave it + // lying around later; that's what the tests are for! + const std::string needle = absl::Base64Escape("StrippingTest.Literal"); + LOG(INFO) << "U3RyaXBwaW5nVGVzdC5MaXRlcmFs"; + auto exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + if (absl::LogSeverity::kInfo >= kAbslMinLogLevel) { + EXPECT_THAT(exe.get(), FileHasSubstr(needle)); + } else { + EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle))); + } +} + +TEST_F(StrippingTest, LiteralInExpression) { + // We need to load a copy of the needle string into memory (so we can search + // for it) without leaving it lying around in plaintext in the executable file + // as would happen if we used a literal. We might (or might not) leave it + // lying around later; that's what the tests are for! + const std::string needle = + absl::Base64Escape("StrippingTest.LiteralInExpression"); + LOG(INFO) << absl::StrCat("secret: ", + "U3RyaXBwaW5nVGVzdC5MaXRlcmFsSW5FeHByZXNzaW9u"); + std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + if (absl::LogSeverity::kInfo >= kAbslMinLogLevel) { + EXPECT_THAT(exe.get(), FileHasSubstr(needle)); + } else { + EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle))); + } +} + +TEST_F(StrippingTest, Fatal) { + // We need to load a copy of the needle string into memory (so we can search + // for it) without leaving it lying around in plaintext in the executable file + // as would happen if we used a literal. We might (or might not) leave it + // lying around later; that's what the tests are for! + const std::string needle = absl::Base64Escape("StrippingTest.Fatal"); + EXPECT_DEATH_IF_SUPPORTED(LOG(FATAL) << "U3RyaXBwaW5nVGVzdC5GYXRhbA==", ""); + std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) { + EXPECT_THAT(exe.get(), FileHasSubstr(needle)); + } else { + EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle))); + } +} + +TEST_F(StrippingTest, Level) { + const std::string needle = absl::Base64Escape("StrippingTest.Level"); + volatile auto severity = absl::LogSeverity::kWarning; + // Ensure that `severity` is not a compile-time constant to prove that + // stripping works regardless: + LOG(LEVEL(severity)) << "U3RyaXBwaW5nVGVzdC5MZXZlbA=="; + std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) { + // This can't be stripped at compile-time because it might evaluate to a + // level that shouldn't be stripped. + EXPECT_THAT(exe.get(), FileHasSubstr(needle)); + } else { +#if defined(_MSC_VER) || defined(__APPLE__) + // Dead code elimination misses this case. +#else + // All levels should be stripped, so it doesn't matter what the severity + // winds up being. + EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle))); +#endif + } +} + +} // namespace |