Skip to content

Commit 66f29f2

Browse files
feat(logging): add Loggers registry (6/6)
Final block: configuration-driven backend selection, mirroring MetricsReporters. - Loggers::Register(type, factory) registers a named backend; Loggers::Load(props) builds one, selecting the type from the "logger-impl" property key. - Built-in factories: "noop", "cerr", and (only when built with ICEBERG_SPDLOG) "spdlog". With no logger-impl set, the default is spdlog when compiled in, else cerr -- logs by default, an intentional divergence from the metrics registry's noop default. - Loggers::LoadAndSetDefault(props) loads a logger and installs it as the process default. This completes the system end to end: levels -> Logger interface + default logger -> CerrLogger/SpdLogger backends -> macros -> configuration-driven selection. loggers_test covers load default/noop/cerr, unknown-type errors, empty-factory rejection, custom Register, and LoadAndSetDefault. Adds logging_end_to_end_test, which drives the public surface as an application does -- now that every layer is present: configure a backend via the registry, install it as the default, log through the LOG_* macros, and observe real output. Covers registry -> default-slot -> macro -> backend -> std::cerr output, level filtering through the full macro path, the compiled-backend identity of the default (spdlog when ON, cerr when OFF), the "spdlog" factory by name, and a macro statement reaching a real spdlog sink. Co-authored-by: Isaac
1 parent 7c38bd4 commit 66f29f2

9 files changed

Lines changed: 545 additions & 0 deletions

File tree

src/iceberg/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ set(ICEBERG_SOURCES
5656
location_provider.cc
5757
logging/cerr_logger.cc
5858
logging/logger.cc
59+
logging/loggers.cc
5960
logging/spdlog_logger.cc
6061
manifest/manifest_adapter.cc
6162
manifest/manifest_entry.cc

src/iceberg/logging/loggers.cc

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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/loggers.h"
21+
22+
#include <exception>
23+
#include <memory>
24+
#include <mutex>
25+
#include <shared_mutex>
26+
#include <string>
27+
#include <unordered_map>
28+
#include <utility>
29+
30+
// Build-generated, .cc-only. Defines ICEBERG_HAS_SPDLOG; tested with #ifdef.
31+
#include "iceberg/logging/cerr_logger.h"
32+
#include "iceberg/logging/config.h"
33+
#include "iceberg/util/macros.h"
34+
#ifdef ICEBERG_HAS_SPDLOG
35+
# include "iceberg/logging/spdlog_logger_internal.h"
36+
#endif
37+
38+
namespace iceberg {
39+
40+
namespace {
41+
42+
/// \brief Registry-constructible no-op logger (Load returns unique_ptr).
43+
class NoopLogger final : public Logger {
44+
public:
45+
bool ShouldLog(LogLevel /*level*/) const noexcept override { return false; }
46+
void Log(LogMessage&& /*message*/) noexcept override {}
47+
void SetLevel(LogLevel /*level*/) noexcept override {}
48+
LogLevel level() const noexcept override { return LogLevel::kOff; }
49+
bool IsNoop() const override { return true; }
50+
};
51+
52+
/// \brief Extract the logger type, defaulting to the compiled-in backend.
53+
std::string InferLoggerType(
54+
const std::unordered_map<std::string, std::string>& properties) {
55+
auto it = properties.find(std::string(kLoggerImpl));
56+
if (it != properties.end() && !it->second.empty()) {
57+
return it->second;
58+
}
59+
#ifdef ICEBERG_HAS_SPDLOG
60+
return std::string(kLoggerTypeSpdlog);
61+
#else
62+
return std::string(kLoggerTypeCerr);
63+
#endif
64+
}
65+
66+
struct LoggerRegistryState {
67+
std::shared_mutex mtx;
68+
std::unordered_map<std::string, LoggerFactory> map;
69+
};
70+
71+
LoggerRegistryState& GetRegistry() {
72+
static auto* state =
73+
new LoggerRegistryState{.map = {
74+
{std::string(kLoggerTypeNoop),
75+
[](const std::unordered_map<std::string, std::string>&)
76+
-> Result<std::unique_ptr<Logger>> {
77+
return std::make_unique<NoopLogger>();
78+
}},
79+
{std::string(kLoggerTypeCerr),
80+
[](const std::unordered_map<std::string, std::string>&)
81+
-> Result<std::unique_ptr<Logger>> {
82+
return std::make_unique<CerrLogger>();
83+
}},
84+
#ifdef ICEBERG_HAS_SPDLOG
85+
{std::string(kLoggerTypeSpdlog),
86+
[](const std::unordered_map<std::string, std::string>&)
87+
-> Result<std::unique_ptr<Logger>> {
88+
return std::make_unique<internal::SpdLogger>();
89+
}},
90+
#endif
91+
}};
92+
return *state;
93+
}
94+
95+
} // namespace
96+
97+
Status Loggers::Register(std::string_view logger_type, LoggerFactory factory) {
98+
if (!factory) {
99+
return InvalidArgument("Logger factory for '{}' must not be empty", logger_type);
100+
}
101+
auto& registry = GetRegistry();
102+
std::unique_lock lock(registry.mtx);
103+
registry.map[std::string(logger_type)] = std::move(factory);
104+
return {};
105+
}
106+
107+
Result<std::unique_ptr<Logger>> Loggers::Load(
108+
const std::unordered_map<std::string, std::string>& properties) {
109+
std::string logger_type = InferLoggerType(properties);
110+
111+
LoggerFactory factory;
112+
{
113+
auto& registry = GetRegistry();
114+
std::shared_lock lock(registry.mtx);
115+
auto it = registry.map.find(logger_type);
116+
if (it == registry.map.end()) {
117+
return InvalidArgument(
118+
"Unknown logger type '{}'. Register a factory with Loggers::Register() "
119+
"before using this type.",
120+
logger_type);
121+
}
122+
factory = it->second;
123+
}
124+
125+
try {
126+
ICEBERG_ASSIGN_OR_RAISE(auto logger, factory(properties));
127+
if (!logger) {
128+
return InvalidArgument("Logger factory for '{}' returned null", logger_type);
129+
}
130+
ICEBERG_RETURN_UNEXPECTED(logger->Initialize(properties));
131+
return logger;
132+
} catch (const std::exception& ex) {
133+
return InvalidArgument("Logger factory for '{}' failed: {}", logger_type, ex.what());
134+
} catch (...) {
135+
return InvalidArgument("Logger factory for '{}' failed with unknown exception",
136+
logger_type);
137+
}
138+
}
139+
140+
Status Loggers::LoadAndSetDefault(
141+
const std::unordered_map<std::string, std::string>& properties) {
142+
ICEBERG_ASSIGN_OR_RAISE(auto logger, Load(properties));
143+
SetDefaultLogger(std::shared_ptr<Logger>(std::move(logger)));
144+
return {};
145+
}
146+
147+
} // namespace iceberg

src/iceberg/logging/loggers.h

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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/loggers.h
23+
/// \brief Property-driven registry/factory for Logger backends.
24+
25+
#include <functional>
26+
#include <memory>
27+
#include <string>
28+
#include <string_view>
29+
#include <unordered_map>
30+
31+
#include "iceberg/iceberg_export.h"
32+
#include "iceberg/logging/logger.h"
33+
#include "iceberg/result.h"
34+
35+
namespace iceberg {
36+
37+
/// \brief Property key selecting the logger implementation.
38+
constexpr std::string_view kLoggerImpl = "logger-impl";
39+
/// \brief Built-in logger type identifiers.
40+
constexpr std::string_view kLoggerTypeNoop = "noop";
41+
constexpr std::string_view kLoggerTypeCerr = "cerr";
42+
constexpr std::string_view kLoggerTypeSpdlog = "spdlog";
43+
44+
/// \brief Factory constructing a Logger from catalog-style properties.
45+
using LoggerFactory = std::function<Result<std::unique_ptr<Logger>>(
46+
const std::unordered_map<std::string, std::string>& properties)>;
47+
48+
/// \brief Registry of logger factories, mirroring MetricsReporters.
49+
///
50+
/// Built-in factories: "noop", "cerr", and (only when built with ICEBERG_SPDLOG)
51+
/// "spdlog". When the "logger-impl" property is absent, the default is "spdlog"
52+
/// if compiled in, otherwise "cerr" -- an intentional divergence from the metrics
53+
/// registry's noop default (we want logs by default).
54+
class ICEBERG_EXPORT Loggers {
55+
public:
56+
/// \brief Construct and initialize a logger from properties.
57+
static Result<std::unique_ptr<Logger>> Load(
58+
const std::unordered_map<std::string, std::string>& properties);
59+
60+
/// \brief Register a factory for \p logger_type (overwrites any existing).
61+
static Status Register(std::string_view logger_type, LoggerFactory factory);
62+
63+
/// \brief Load a logger from properties and install it as the default.
64+
static Status LoadAndSetDefault(
65+
const std::unordered_map<std::string, std::string>& properties);
66+
};
67+
68+
} // namespace iceberg

src/iceberg/logging/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ install_headers(
2929
'log_level.h',
3030
'log_macros.h',
3131
'logger.h',
32+
'loggers.h',
3233
'short_log_macros.h',
3334
],
3435
subdir: 'iceberg/logging',

src/iceberg/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ iceberg_sources = files(
108108
'location_provider.cc',
109109
'logging/cerr_logger.cc',
110110
'logging/logger.cc',
111+
'logging/loggers.cc',
111112
'logging/spdlog_logger.cc',
112113
'manifest/manifest_adapter.cc',
113114
'manifest/manifest_entry.cc',

src/iceberg/test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ add_iceberg_test(logging_test
9696
cerr_logger_test.cc
9797
log_level_test.cc
9898
logger_test.cc
99+
loggers_test.cc
100+
logging_end_to_end_test.cc
99101
macros_active_level_test.cc
100102
macros_test.cc
101103
spdlog_logger_test.cc)

0 commit comments

Comments
 (0)