From 91bfd46d20eca5c9191e33ddd2b1c74c0bc1afda Mon Sep 17 00:00:00 2001 From: Kam Cheung Ting Date: Mon, 15 Jun 2026 10:47:10 +0000 Subject: [PATCH] feat(logging): add LogLevel severity enum (1/6) First block of the iceberg-cpp logging system. Introduces the severity level type the rest of the stack builds on: - LogLevel { kTrace, kDebug, kInfo, kWarn, kError, kCritical, kFatal, kOff }, ordered so `level >= threshold` is the enabled test; kOff is the max sentinel (disables all emission as a threshold, never an actual message level). - constexpr ToString() and case-insensitive LogLevelFromString() -> Result, mirroring the CounterUnit pattern in src/iceberg/metrics/. Wires the new logging/ subdirectory and a logging_test target into BOTH the CMake and Meson builds (header install + test), so the two build systems stay at parity from the first block. Co-authored-by: Isaac --- src/iceberg/CMakeLists.txt | 1 + src/iceberg/logging/CMakeLists.txt | 18 ++++++ src/iceberg/logging/log_level.h | 90 +++++++++++++++++++++++++++++ src/iceberg/logging/meson.build | 18 ++++++ src/iceberg/meson.build | 2 + src/iceberg/test/.meson.build.swp | Bin 0 -> 20480 bytes src/iceberg/test/CMakeLists.txt | 2 + src/iceberg/test/log_level_test.cc | 78 +++++++++++++++++++++++++ src/iceberg/test/meson.build | 1 + 9 files changed, 210 insertions(+) create mode 100644 src/iceberg/logging/CMakeLists.txt create mode 100644 src/iceberg/logging/log_level.h create mode 100644 src/iceberg/logging/meson.build create mode 100644 src/iceberg/test/.meson.build.swp create mode 100644 src/iceberg/test/log_level_test.cc diff --git a/src/iceberg/CMakeLists.txt b/src/iceberg/CMakeLists.txt index 0fe418d48..ba51b57cb 100644 --- a/src/iceberg/CMakeLists.txt +++ b/src/iceberg/CMakeLists.txt @@ -239,6 +239,7 @@ add_subdirectory(row) add_subdirectory(update) add_subdirectory(util) add_subdirectory(metrics) +add_subdirectory(logging) if(ICEBERG_BUILD_BUNDLE) set(ICEBERG_BUNDLE_SOURCES diff --git a/src/iceberg/logging/CMakeLists.txt b/src/iceberg/logging/CMakeLists.txt new file mode 100644 index 000000000..75b869908 --- /dev/null +++ b/src/iceberg/logging/CMakeLists.txt @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +iceberg_install_all_headers(iceberg/logging) diff --git a/src/iceberg/logging/log_level.h b/src/iceberg/logging/log_level.h new file mode 100644 index 000000000..d06916af0 --- /dev/null +++ b/src/iceberg/logging/log_level.h @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once + +/// \file iceberg/logging/log_level.h +/// \brief Severity levels for the logging system. + +#include +#include + +#include "iceberg/result.h" +#include "iceberg/util/string_util.h" + +namespace iceberg { + +/// \brief Logging severity level, ordered from most to least verbose. +/// +/// Levels are ordered so that `level >= threshold` is the enabled test. +/// `kOff` is the maximum sentinel: as a threshold it disables all emission +/// (it is never the level of an actual message). +enum class LogLevel { + kTrace, + kDebug, + kInfo, + kWarn, + kError, + kCritical, + kFatal, + kOff, +}; + +/// \brief String representation of a LogLevel. +constexpr std::string_view ToString(LogLevel level) noexcept { + switch (level) { + case LogLevel::kTrace: + return "trace"; + case LogLevel::kDebug: + return "debug"; + case LogLevel::kInfo: + return "info"; + case LogLevel::kWarn: + return "warn"; + case LogLevel::kError: + return "error"; + case LogLevel::kCritical: + return "critical"; + case LogLevel::kFatal: + return "fatal"; + case LogLevel::kOff: + return "off"; + } + std::unreachable(); +} + +/// \brief Parse a LogLevel from a string (case-insensitive). +/// +/// \param s The string to parse ("trace", "debug", "info", "warn", "error", +/// "critical", "fatal", or "off"). +/// \return The LogLevel, or an InvalidArgument error if unrecognized. +inline Result LogLevelFromString(std::string_view s) { + auto level = StringUtils::ToLower(s); + if (level == "trace") return LogLevel::kTrace; + if (level == "debug") return LogLevel::kDebug; + if (level == "info") return LogLevel::kInfo; + if (level == "warn") return LogLevel::kWarn; + if (level == "error") return LogLevel::kError; + if (level == "critical") return LogLevel::kCritical; + if (level == "fatal") return LogLevel::kFatal; + if (level == "off") return LogLevel::kOff; + return InvalidArgument("Invalid log level: {}", s); +} + +} // namespace iceberg diff --git a/src/iceberg/logging/meson.build b/src/iceberg/logging/meson.build new file mode 100644 index 000000000..3c286a196 --- /dev/null +++ b/src/iceberg/logging/meson.build @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +install_headers(['log_level.h'], subdir: 'iceberg/logging') diff --git a/src/iceberg/meson.build b/src/iceberg/meson.build index 8c4b49e9b..edc470991 100644 --- a/src/iceberg/meson.build +++ b/src/iceberg/meson.build @@ -38,6 +38,8 @@ configure_file( install_dir: get_option('includedir') / 'iceberg', ) +subdir('logging') + iceberg_include_dir = include_directories('..') iceberg_sources = files( 'arrow_c_data_guard_internal.cc', diff --git a/src/iceberg/test/.meson.build.swp b/src/iceberg/test/.meson.build.swp new file mode 100644 index 0000000000000000000000000000000000000000..58638fe24ea751df85d651a182f4d1b25ae5c16e GIT binary patch literal 20480 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 Y=EcT*^5_|3UTickHtN?eyHS*X1Nv>_5C8xG literal 0 HcmV?d00001 diff --git a/src/iceberg/test/CMakeLists.txt b/src/iceberg/test/CMakeLists.txt index 0e8f03150..ad62042ce 100644 --- a/src/iceberg/test/CMakeLists.txt +++ b/src/iceberg/test/CMakeLists.txt @@ -101,6 +101,8 @@ add_iceberg_test(table_test table_test.cc table_update_test.cc) +add_iceberg_test(logging_test SOURCES log_level_test.cc) + add_iceberg_test(expression_test SOURCES aggregate_test.cc diff --git a/src/iceberg/test/log_level_test.cc b/src/iceberg/test/log_level_test.cc new file mode 100644 index 000000000..e4c25bf67 --- /dev/null +++ b/src/iceberg/test/log_level_test.cc @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "iceberg/logging/log_level.h" + +#include +#include + +#include + +namespace iceberg { + +namespace { + +constexpr std::array kAllLevels = { + LogLevel::kTrace, LogLevel::kDebug, LogLevel::kInfo, LogLevel::kWarn, + LogLevel::kError, LogLevel::kCritical, LogLevel::kFatal, LogLevel::kOff}; + +} // namespace + +TEST(LogLevelTest, ToStringCoversEveryLevel) { + EXPECT_EQ(ToString(LogLevel::kTrace), "trace"); + EXPECT_EQ(ToString(LogLevel::kDebug), "debug"); + EXPECT_EQ(ToString(LogLevel::kInfo), "info"); + EXPECT_EQ(ToString(LogLevel::kWarn), "warn"); + EXPECT_EQ(ToString(LogLevel::kError), "error"); + EXPECT_EQ(ToString(LogLevel::kCritical), "critical"); + EXPECT_EQ(ToString(LogLevel::kFatal), "fatal"); + EXPECT_EQ(ToString(LogLevel::kOff), "off"); +} + +TEST(LogLevelTest, FromStringRoundTrips) { + for (LogLevel level : kAllLevels) { + auto parsed = LogLevelFromString(ToString(level)); + ASSERT_TRUE(parsed.has_value()) << "failed to parse " << ToString(level); + EXPECT_EQ(parsed.value(), level); + } +} + +TEST(LogLevelTest, FromStringIsCaseInsensitive) { + EXPECT_EQ(LogLevelFromString("WARN").value(), LogLevel::kWarn); + EXPECT_EQ(LogLevelFromString("Warn").value(), LogLevel::kWarn); + EXPECT_EQ(LogLevelFromString("CRITICAL").value(), LogLevel::kCritical); +} + +TEST(LogLevelTest, FromStringRejectsUnknown) { + auto parsed = LogLevelFromString("verbose"); + ASSERT_FALSE(parsed.has_value()); + EXPECT_EQ(parsed.error().kind, ErrorKind::kInvalidArgument); +} + +TEST(LogLevelTest, OrderingIsMonotonicWithOffAsMaximum) { + EXPECT_LT(LogLevel::kTrace, LogLevel::kDebug); + EXPECT_LT(LogLevel::kDebug, LogLevel::kInfo); + EXPECT_LT(LogLevel::kInfo, LogLevel::kWarn); + EXPECT_LT(LogLevel::kWarn, LogLevel::kError); + EXPECT_LT(LogLevel::kError, LogLevel::kCritical); + EXPECT_LT(LogLevel::kCritical, LogLevel::kFatal); + EXPECT_LT(LogLevel::kFatal, LogLevel::kOff); +} + +} // namespace iceberg diff --git a/src/iceberg/test/meson.build b/src/iceberg/test/meson.build index 03d9e1f6c..050fe71ca 100644 --- a/src/iceberg/test/meson.build +++ b/src/iceberg/test/meson.build @@ -60,6 +60,7 @@ iceberg_tests = { 'table_update_test.cc', ), }, + 'logging_test': {'sources': files('log_level_test.cc')}, 'expression_test': { 'sources': files( 'aggregate_test.cc',