Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/iceberg/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ set(ICEBERG_SOURCES
location_provider.cc
logging/cerr_logger.cc
logging/logger.cc
logging/loggers.cc
logging/spdlog_logger.cc
manifest/manifest_adapter.cc
manifest/manifest_entry.cc
Expand Down
4 changes: 3 additions & 1 deletion src/iceberg/logging/logger.cc
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ struct ThreadCache {
std::shared_ptr<Logger> Logger::Noop() {
// Intentionally leaked: reachable via the function-local static (LSan-clean)
// and never destroyed, so logging during static teardown stays safe.
static auto* instance = new std::shared_ptr<Logger>(std::make_shared<NoopLogger>());
static auto* instance = new std::shared_ptr<Logger>(internal::MakeNoopLogger());
return *instance;
}

Expand Down Expand Up @@ -141,6 +141,8 @@ FatalHandler GetFatalHandler() {

namespace internal {

std::unique_ptr<Logger> MakeNoopLogger() { return std::make_unique<NoopLogger>(); }

namespace {

/// \brief The one place the per-thread cache's lifetime is managed; shared by
Expand Down
4 changes: 4 additions & 0 deletions src/iceberg/logging/logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ class ICEBERG_EXPORT ScopedLogger {

namespace internal {

/// \brief Construct a fresh no-op logger. Shared by Logger::Noop() (which caches a
/// single instance) and the "noop" registry factory (which needs an owned one).
ICEBERG_EXPORT std::unique_ptr<Logger> MakeNoopLogger();

/// \brief Hot-path accessor for the default logger.
///
/// Returns a reference to a thread-local cached shared_ptr that is refreshed
Expand Down
140 changes: 140 additions & 0 deletions src/iceberg/logging/loggers.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* 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/loggers.h"

#include <exception>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <unordered_map>
#include <utility>

// Build-generated, .cc-only. Defines ICEBERG_HAS_SPDLOG; tested with #ifdef.
#include "iceberg/logging/cerr_logger.h"
#include "iceberg/logging/config.h"
#include "iceberg/util/macros.h"
#ifdef ICEBERG_HAS_SPDLOG
# include "iceberg/logging/spdlog_logger_internal.h"
#endif

namespace iceberg {

namespace {

/// \brief Extract the logger type, defaulting to the compiled-in backend.
std::string InferLoggerType(
const std::unordered_map<std::string, std::string>& properties) {
auto it = properties.find(std::string(kLoggerImpl));
if (it != properties.end() && !it->second.empty()) {
return it->second;
}
#ifdef ICEBERG_HAS_SPDLOG
return std::string(kLoggerTypeSpdlog);
#else
return std::string(kLoggerTypeCerr);
#endif
}

struct LoggerRegistryState {
std::shared_mutex mtx;
std::unordered_map<std::string, LoggerFactory> map;
};

LoggerRegistryState& GetRegistry() {
static auto* state =
new LoggerRegistryState{.map = {
{std::string(kLoggerTypeNoop),
[](const std::unordered_map<std::string, std::string>&)
-> Result<std::unique_ptr<Logger>> {
return internal::MakeNoopLogger();
}},
{std::string(kLoggerTypeCerr),
[](const std::unordered_map<std::string, std::string>&)
-> Result<std::unique_ptr<Logger>> {
return std::make_unique<CerrLogger>();
}},
#ifdef ICEBERG_HAS_SPDLOG
{std::string(kLoggerTypeSpdlog),
[](const std::unordered_map<std::string, std::string>&)
-> Result<std::unique_ptr<Logger>> {
return std::make_unique<internal::SpdLogger>();
}},
#endif
}};
return *state;
}

} // namespace

Status Loggers::Register(std::string_view logger_type, LoggerFactory factory) {
if (!factory) {
return InvalidArgument("Logger factory for '{}' must not be empty", logger_type);
}
auto& registry = GetRegistry();
std::unique_lock lock(registry.mtx);
registry.map[std::string(logger_type)] = std::move(factory);
return {};
}

Result<std::unique_ptr<Logger>> Loggers::Load(
const std::unordered_map<std::string, std::string>& properties) {
std::string logger_type = InferLoggerType(properties);

LoggerFactory factory;
{
auto& registry = GetRegistry();
std::shared_lock lock(registry.mtx);
auto it = registry.map.find(logger_type);
if (it == registry.map.end()) {
return InvalidArgument(
"Unknown logger type '{}'. Register a factory with Loggers::Register() "
"before using this type.",
logger_type);
}
factory = it->second;
}

try {
// Run the (user-supplied) factory outside the registry lock so it cannot
// deadlock or re-enter the registry; the try/catch turns a throwing factory
// into an error instead of propagating.
ICEBERG_ASSIGN_OR_RAISE(auto logger, factory(properties));
if (!logger) {
return InvalidArgument("Logger factory for '{}' returned null", logger_type);
}
ICEBERG_RETURN_UNEXPECTED(logger->Initialize(properties));
return logger;
} catch (const std::exception& ex) {
return InvalidArgument("Logger factory for '{}' failed: {}", logger_type, ex.what());
} catch (...) {
return InvalidArgument("Logger factory for '{}' failed with unknown exception",
logger_type);
}
}

Status Loggers::LoadAndSetDefault(
const std::unordered_map<std::string, std::string>& properties) {
ICEBERG_ASSIGN_OR_RAISE(auto logger, Load(properties));
SetDefaultLogger(std::shared_ptr<Logger>(std::move(logger)));
return {};
}

} // namespace iceberg
68 changes: 68 additions & 0 deletions src/iceberg/logging/loggers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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/loggers.h
/// \brief Property-driven registry/factory for Logger backends.

#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>

#include "iceberg/iceberg_export.h"
#include "iceberg/logging/logger.h"
#include "iceberg/result.h"

namespace iceberg {

/// \brief Property key selecting the logger implementation.
constexpr std::string_view kLoggerImpl = "logger-impl";
/// \brief Built-in logger type identifiers.
constexpr std::string_view kLoggerTypeNoop = "noop";
constexpr std::string_view kLoggerTypeCerr = "cerr";
constexpr std::string_view kLoggerTypeSpdlog = "spdlog";

/// \brief Factory constructing a Logger from catalog-style properties.
using LoggerFactory = std::function<Result<std::unique_ptr<Logger>>(
const std::unordered_map<std::string, std::string>& properties)>;

/// \brief Registry of logger factories, mirroring MetricsReporters.
///
/// Built-in factories: "noop", "cerr", and (only when built with ICEBERG_SPDLOG)
/// "spdlog". When the "logger-impl" property is absent, the default is "spdlog"
/// if compiled in, otherwise "cerr" -- an intentional divergence from the metrics
/// registry's noop default (we want logs by default).
class ICEBERG_EXPORT Loggers {
public:
/// \brief Construct and initialize a logger from properties.
static Result<std::unique_ptr<Logger>> Load(
const std::unordered_map<std::string, std::string>& properties);

/// \brief Register a factory for \p logger_type (overwrites any existing).
static Status Register(std::string_view logger_type, LoggerFactory factory);

/// \brief Load a logger from properties and install it as the default.
static Status LoadAndSetDefault(
const std::unordered_map<std::string, std::string>& properties);
};

} // namespace iceberg
1 change: 1 addition & 0 deletions src/iceberg/logging/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ install_headers(
'log_level.h',
'log_macros.h',
'logger.h',
'loggers.h',
'short_log_macros.h',
],
subdir: 'iceberg/logging',
Expand Down
1 change: 1 addition & 0 deletions src/iceberg/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ iceberg_sources = files(
'location_provider.cc',
'logging/cerr_logger.cc',
'logging/logger.cc',
'logging/loggers.cc',
'logging/spdlog_logger.cc',
'manifest/manifest_adapter.cc',
'manifest/manifest_entry.cc',
Expand Down
2 changes: 2 additions & 0 deletions src/iceberg/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ add_iceberg_test(logging_test
cerr_logger_test.cc
log_level_test.cc
logger_test.cc
loggers_test.cc
logging_end_to_end_test.cc
macros_active_level_test.cc
macros_test.cc
spdlog_logger_test.cc)
Expand Down
Loading
Loading