Skip to content

Commit 9b32a4a

Browse files
Add log levels for the new logging system
This is the first of six small PRs that together add a logging system to iceberg-cpp. It introduces LogLevel, the severity scale everything else builds on: trace, debug, info, warn, error, critical, fatal, plus an `off` sentinel for turning logging off entirely. Levels are ordered from most to least verbose, so deciding whether something should be logged is a plain `level >= threshold` comparison. Two helpers come with it: ToString to print a level, and LogLevelFromString to parse one (case-insensitive, returning a Result for unrecognized input). Both follow the same shape as CounterUnit in metrics/counter.h. There is no implementation behind the header yet, so this PR only adds the header, wires the new src/iceberg/logging directory into the build, and adds a test covering the round-trip, ordering, and parsing. Co-authored-by: Isaac
1 parent d3b02bb commit 9b32a4a

5 files changed

Lines changed: 190 additions & 0 deletions

File tree

src/iceberg/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ add_subdirectory(row)
239239
add_subdirectory(update)
240240
add_subdirectory(util)
241241
add_subdirectory(metrics)
242+
add_subdirectory(logging)
242243

243244
if(ICEBERG_BUILD_BUNDLE)
244245
set(ICEBERG_BUNDLE_SOURCES

src/iceberg/logging/CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
iceberg_install_all_headers(iceberg/logging)

src/iceberg/logging/log_level.h

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#pragma once
21+
22+
/// \file iceberg/logging/log_level.h
23+
/// \brief Severity levels for the logging system.
24+
25+
#include <string_view>
26+
#include <utility>
27+
28+
#include "iceberg/iceberg_export.h"
29+
#include "iceberg/result.h"
30+
#include "iceberg/util/string_util.h"
31+
32+
namespace iceberg {
33+
34+
/// \brief Logging severity level, ordered from most to least verbose.
35+
///
36+
/// Levels are ordered so that `level >= threshold` is the enabled test.
37+
/// `kOff` is the maximum sentinel: as a threshold it disables all emission
38+
/// (it is never the level of an actual message).
39+
enum class LogLevel {
40+
kTrace,
41+
kDebug,
42+
kInfo,
43+
kWarn,
44+
kError,
45+
kCritical,
46+
kFatal,
47+
kOff,
48+
};
49+
50+
/// \brief String representation of a LogLevel.
51+
ICEBERG_EXPORT constexpr std::string_view ToString(LogLevel level) noexcept {
52+
switch (level) {
53+
case LogLevel::kTrace:
54+
return "trace";
55+
case LogLevel::kDebug:
56+
return "debug";
57+
case LogLevel::kInfo:
58+
return "info";
59+
case LogLevel::kWarn:
60+
return "warn";
61+
case LogLevel::kError:
62+
return "error";
63+
case LogLevel::kCritical:
64+
return "critical";
65+
case LogLevel::kFatal:
66+
return "fatal";
67+
case LogLevel::kOff:
68+
return "off";
69+
}
70+
std::unreachable();
71+
}
72+
73+
/// \brief Parse a LogLevel from a string (case-insensitive).
74+
///
75+
/// \param s The string to parse ("trace", "debug", "info", "warn", "error",
76+
/// "critical", "fatal", or "off").
77+
/// \return The LogLevel, or an InvalidArgument error if unrecognized.
78+
ICEBERG_EXPORT inline Result<LogLevel> LogLevelFromString(std::string_view s) {
79+
auto level = StringUtils::ToLower(s);
80+
if (level == "trace") return LogLevel::kTrace;
81+
if (level == "debug") return LogLevel::kDebug;
82+
if (level == "info") return LogLevel::kInfo;
83+
if (level == "warn") return LogLevel::kWarn;
84+
if (level == "error") return LogLevel::kError;
85+
if (level == "critical") return LogLevel::kCritical;
86+
if (level == "fatal") return LogLevel::kFatal;
87+
if (level == "off") return LogLevel::kOff;
88+
return InvalidArgument("Invalid log level: {}", s);
89+
}
90+
91+
} // namespace iceberg

src/iceberg/test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ add_iceberg_test(table_test
101101
table_test.cc
102102
table_update_test.cc)
103103

104+
add_iceberg_test(logging_test SOURCES log_level_test.cc)
105+
104106
add_iceberg_test(expression_test
105107
SOURCES
106108
aggregate_test.cc

src/iceberg/test/log_level_test.cc

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include "iceberg/logging/log_level.h"
21+
22+
#include <array>
23+
#include <string>
24+
25+
#include <gtest/gtest.h>
26+
27+
namespace iceberg {
28+
29+
namespace {
30+
31+
constexpr std::array<LogLevel, 8> kAllLevels = {
32+
LogLevel::kTrace, LogLevel::kDebug, LogLevel::kInfo, LogLevel::kWarn,
33+
LogLevel::kError, LogLevel::kCritical, LogLevel::kFatal, LogLevel::kOff};
34+
35+
} // namespace
36+
37+
TEST(LogLevelTest, ToStringCoversEveryLevel) {
38+
EXPECT_EQ(ToString(LogLevel::kTrace), "trace");
39+
EXPECT_EQ(ToString(LogLevel::kDebug), "debug");
40+
EXPECT_EQ(ToString(LogLevel::kInfo), "info");
41+
EXPECT_EQ(ToString(LogLevel::kWarn), "warn");
42+
EXPECT_EQ(ToString(LogLevel::kError), "error");
43+
EXPECT_EQ(ToString(LogLevel::kCritical), "critical");
44+
EXPECT_EQ(ToString(LogLevel::kFatal), "fatal");
45+
EXPECT_EQ(ToString(LogLevel::kOff), "off");
46+
}
47+
48+
TEST(LogLevelTest, FromStringRoundTrips) {
49+
for (LogLevel level : kAllLevels) {
50+
auto parsed = LogLevelFromString(ToString(level));
51+
ASSERT_TRUE(parsed.has_value()) << "failed to parse " << ToString(level);
52+
EXPECT_EQ(parsed.value(), level);
53+
}
54+
}
55+
56+
TEST(LogLevelTest, FromStringIsCaseInsensitive) {
57+
EXPECT_EQ(LogLevelFromString("WARN").value(), LogLevel::kWarn);
58+
EXPECT_EQ(LogLevelFromString("Warn").value(), LogLevel::kWarn);
59+
EXPECT_EQ(LogLevelFromString("CRITICAL").value(), LogLevel::kCritical);
60+
}
61+
62+
TEST(LogLevelTest, FromStringRejectsUnknown) {
63+
auto parsed = LogLevelFromString("verbose");
64+
ASSERT_FALSE(parsed.has_value());
65+
EXPECT_EQ(parsed.error().kind, ErrorKind::kInvalidArgument);
66+
}
67+
68+
TEST(LogLevelTest, OrderingIsMonotonicWithOffAsMaximum) {
69+
EXPECT_LT(LogLevel::kTrace, LogLevel::kDebug);
70+
EXPECT_LT(LogLevel::kDebug, LogLevel::kInfo);
71+
EXPECT_LT(LogLevel::kInfo, LogLevel::kWarn);
72+
EXPECT_LT(LogLevel::kWarn, LogLevel::kError);
73+
EXPECT_LT(LogLevel::kError, LogLevel::kCritical);
74+
EXPECT_LT(LogLevel::kCritical, LogLevel::kFatal);
75+
EXPECT_LT(LogLevel::kFatal, LogLevel::kOff);
76+
}
77+
78+
} // namespace iceberg

0 commit comments

Comments
 (0)