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 000000000..58638fe24 Binary files /dev/null and b/src/iceberg/test/.meson.build.swp differ 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',