Skip to content

Commit

Permalink
feat: add spdlog sink for bedrock log integration
Browse files Browse the repository at this point in the history
  • Loading branch information
wu-vincent committed Feb 1, 2024
1 parent 52bf438 commit 62ed16e
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 73 deletions.
4 changes: 2 additions & 2 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ CheckOptions:
readability-identifier-naming.ClassMethodCase: camelCase
readability-identifier-naming.ConstantCase: lower_case
readability-identifier-naming.ConstantMemberCase: lower_case
readability-identifier-naming.ConstantMemberSuffix: '_'
readability-identifier-naming.ConstantMemberSuffix: ''
readability-identifier-naming.ConstantParameterCase: lower_case
readability-identifier-naming.ConstantPointerParameterCase: lower_case
readability-identifier-naming.ConstexprFunctionCase: aNy_CasE
Expand Down Expand Up @@ -70,7 +70,7 @@ CheckOptions:
readability-identifier-naming.PublicMemberSuffix: ''
readability-identifier-naming.PublicMethodCase: camelCase
readability-identifier-naming.ScopedEnumConstantCase: CamelCase
readability-identifier-naming.StaticConstantCase: CamelCase
readability-identifier-naming.StaticConstantCase: lower_case
readability-identifier-naming.StaticVariableCase: lower_case
readability-identifier-naming.StructCase: CamelCase
readability-identifier-naming.TemplateParameterCase: CamelCase
Expand Down
12 changes: 0 additions & 12 deletions include/endstone_core/logger_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,6 @@

#include "endstone/logger.h"

class SpdLogAdapter : public Logger {
public:
explicit SpdLogAdapter(std::shared_ptr<spdlog::logger> logger);
void setLevel(Level level) override;
[[nodiscard]] bool isEnabledFor(Level level) const override;
[[nodiscard]] std::string_view getName() const override;
void log(Level level, const std::string &message) const override;

private:
std::shared_ptr<spdlog::logger> logger_;
};

class LoggerFactory {
public:
static Logger &getLogger(const std::string &name);
Expand Down
25 changes: 25 additions & 0 deletions include/endstone_core/spdlog/bedrock_level_formatter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved.
//
// Licensed 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

#include <spdlog/pattern_formatter.h>
#include <spdlog/spdlog.h>

class BedrockLevelFormatter : public spdlog::custom_flag_formatter {
public:
void format(const spdlog::details::log_msg &msg, const std::tm &, spdlog::memory_buf_t &dest) override;

[[nodiscard]] std::unique_ptr<custom_flag_formatter> clone() const override;
};
78 changes: 78 additions & 0 deletions include/endstone_core/spdlog/bedrock_log_sink.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved.
//
// Licensed 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

#include <array>
#include <string_view>

#include <spdlog/details/console_globals.h>
#include <spdlog/details/os.h>
#include <spdlog/sinks/base_sink.h>
#include <spdlog/spdlog.h>

class BedrockLogSink : public spdlog::sinks::base_sink<spdlog::details::console_mutex::mutex_t> {
public:
explicit BedrockLogSink(FILE *target_file, spdlog::color_mode mode = spdlog::color_mode::automatic);
void set_color_mode(spdlog::color_mode mode);

protected:
void sink_it_(const spdlog::details::log_msg &msg) override;
void flush_() override;

public:
// Formatting codes
const spdlog::string_view_t reset = "\033[m";
const spdlog::string_view_t bold = "\033[1m";
const spdlog::string_view_t dark = "\033[2m";
const spdlog::string_view_t underline = "\033[4m";
const spdlog::string_view_t blink = "\033[5m";
const spdlog::string_view_t reverse = "\033[7m";
const spdlog::string_view_t concealed = "\033[8m";
const spdlog::string_view_t clear_line = "\033[K";

// Foreground colors
const spdlog::string_view_t black = "\033[30m";
const spdlog::string_view_t red = "\033[31m";
const spdlog::string_view_t green = "\033[32m";
const spdlog::string_view_t yellow = "\033[33m";
const spdlog::string_view_t blue = "\033[34m";
const spdlog::string_view_t magenta = "\033[35m";
const spdlog::string_view_t cyan = "\033[36m";
const spdlog::string_view_t white = "\033[37m";

/// Background colors
const spdlog::string_view_t on_black = "\033[40m";
const spdlog::string_view_t on_red = "\033[41m";
const spdlog::string_view_t on_green = "\033[42m";
const spdlog::string_view_t on_yellow = "\033[43m";
const spdlog::string_view_t on_blue = "\033[44m";
const spdlog::string_view_t on_magenta = "\033[45m";
const spdlog::string_view_t on_cyan = "\033[46m";
const spdlog::string_view_t on_white = "\033[47m";

/// Bold colors
const spdlog::string_view_t yellow_bold = "\033[33m\033[1m";
const spdlog::string_view_t red_bold = "\033[31m\033[1m";
const spdlog::string_view_t bold_on_red = "\033[1m\033[41m";

private:
void print_ccode_(const spdlog::string_view_t &color_code);
void print_range_(const spdlog::memory_buf_t &formatted, size_t start, size_t end);
static std::string to_string(const spdlog::string_view_t &sv);

FILE *target_file_;
bool should_do_colors_;
std::array<std::string, spdlog::level::n_levels> colors_;
};
31 changes: 31 additions & 0 deletions include/endstone_core/spdlog/spdlog_adapter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved.
//
// Licensed 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

#include <spdlog/spdlog.h>

#include "endstone/logger.h"

class SpdLogAdapter : public Logger {
public:
explicit SpdLogAdapter(std::shared_ptr<spdlog::logger> logger);
void setLevel(Level level) override;
[[nodiscard]] bool isEnabledFor(Level level) const override;
[[nodiscard]] std::string_view getName() const override;
void log(Level level, const std::string &message) const override;

private:
std::shared_ptr<spdlog::logger> logger_;
};
62 changes: 3 additions & 59 deletions src/endstone_core/logger_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,59 +17,9 @@
#include <mutex>
#include <string>
#include <unordered_map>
#include <utility>

#include <spdlog/pattern_formatter.h>
#include <spdlog/sinks/ansicolor_sink-inl.h>
#include <spdlog/sinks/ansicolor_sink.h>
#include <spdlog/spdlog.h>

template class SPDLOG_API spdlog::sinks::ansicolor_stdout_sink<spdlog::details::console_mutex>;

class LevelFormatter : public spdlog::custom_flag_formatter {
public:
void format(const spdlog::details::log_msg &msg, const std::tm &, spdlog::memory_buf_t &dest) override
{
static const std::unordered_map<spdlog::level::level_enum, std::string_view> SpdlogLevelNames = {
{spdlog::level::trace, "TRACE"}, {spdlog::level::debug, "DEBUG"}, {spdlog::level::info, "INFO"},
{spdlog::level::warn, "WARNING"}, {spdlog::level::err, "ERROR"}, {spdlog::level::critical, "CRITICAL"},
{spdlog::level::off, "OFF"},
};

const auto level_name = SpdlogLevelNames.find(msg.level)->second;
const auto *buf_ptr = level_name.data();
dest.append(buf_ptr, buf_ptr + level_name.size());
}

[[nodiscard]] std::unique_ptr<custom_flag_formatter> clone() const override
{
return spdlog::details::make_unique<LevelFormatter>();
}
};

SpdLogAdapter::SpdLogAdapter(std::shared_ptr<spdlog::logger> logger) : logger_(std::move(logger)) {}

void SpdLogAdapter::log(Logger::Level level, const std::string &message) const
{
if (isEnabledFor(level)) {
logger_->log(static_cast<spdlog::level::level_enum>(level), message);
}
}

void SpdLogAdapter::setLevel(Logger::Level level)
{
logger_->set_level(static_cast<spdlog::level::level_enum>(level));
}

bool SpdLogAdapter::isEnabledFor(Logger::Level level) const
{
return logger_->should_log(static_cast<spdlog::level::level_enum>(level));
}

std::string_view SpdLogAdapter::getName() const
{
return logger_->name();
}
#include "endstone_core/spdlog/bedrock_log_sink.h"
#include "endstone_core/spdlog/spdlog_adapter.h"

Logger &LoggerFactory::getLogger(const std::string &name)
{
Expand All @@ -82,13 +32,7 @@ Logger &LoggerFactory::getLogger(const std::string &name)
return it->second;
}

auto formatter = std::make_unique<spdlog::pattern_formatter>();
formatter->add_flag<LevelFormatter>('L').set_pattern("%^[%Y-%m-%d %H:%M:%S.%e %L] [%n] %v%$");

auto console_sink = std::make_shared<spdlog::sinks::ansicolor_stdout_sink_mt>();
console_sink->set_formatter(std::move(formatter));
console_sink->set_color(spdlog::level::info, console_sink->reset);

spdlog::sink_ptr console_sink = std::make_shared<BedrockLogSink>(stdout);
auto console = std::make_shared<spdlog::logger>(name, console_sink);
spdlog::register_logger(console);
it = loggers.emplace(name, SpdLogAdapter(console)).first;
Expand Down
33 changes: 33 additions & 0 deletions src/endstone_core/spdlog/bedrock_level_formatter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved.
//
// Licensed 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 "endstone_core/spdlog/bedrock_level_formatter.h"

void BedrockLevelFormatter::format(const spdlog::details::log_msg &msg, const tm &, spdlog::memory_buf_t &dest)
{
static const std::unordered_map<spdlog::level::level_enum, std::string_view> level_names = {
{spdlog::level::trace, "TRACE"}, {spdlog::level::debug, "DEBUG"}, {spdlog::level::info, "INFO"},
{spdlog::level::warn, "WARNING"}, {spdlog::level::err, "ERROR"}, {spdlog::level::critical, "CRITICAL"},
{spdlog::level::off, "OFF"},
};

const auto level_name = level_names.find(msg.level)->second;
const auto *buf_ptr = level_name.data();
dest.append(buf_ptr, buf_ptr + level_name.size());
}

std::unique_ptr<spdlog::custom_flag_formatter> BedrockLevelFormatter::clone() const
{
return spdlog::details::make_unique<BedrockLevelFormatter>();
}
96 changes: 96 additions & 0 deletions src/endstone_core/spdlog/bedrock_log_sink.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved.
//
// Licensed 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 "endstone_core/spdlog/bedrock_log_sink.h"

#include <spdlog/pattern_formatter.h>
#include <spdlog/sinks/base_sink-inl.h>

#include "endstone_core/spdlog/bedrock_level_formatter.h"

BedrockLogSink::BedrockLogSink(FILE *target_file, spdlog::color_mode mode)
: target_file_(target_file), spdlog::sinks::base_sink<spdlog::details::console_mutex::mutex_t>(
spdlog::details::make_unique<spdlog::pattern_formatter>())
{
auto *formatter = dynamic_cast<spdlog::pattern_formatter *>(formatter_.get());
formatter->add_flag<BedrockLevelFormatter>('L');
formatter->set_pattern("%^[%Y-%m-%d %H:%M:%S.%e %L] [%n] %v%$");
// TODO: replace minecraft color format with ansi code
set_color_mode(mode);
colors_.at(spdlog::level::trace) = to_string(white);
colors_.at(spdlog::level::debug) = to_string(cyan);
colors_.at(spdlog::level::info) = to_string(reset);
colors_.at(spdlog::level::warn) = to_string(yellow_bold);
colors_.at(spdlog::level::err) = to_string(red_bold);
colors_.at(spdlog::level::critical) = to_string(bold_on_red);
colors_.at(spdlog::level::off) = to_string(reset);
}

void BedrockLogSink::set_color_mode(spdlog::color_mode mode)
{
switch (mode) {
case spdlog::color_mode::always:
should_do_colors_ = true;
return;
case spdlog::color_mode::automatic:
should_do_colors_ = spdlog::details::os::in_terminal(target_file_) && spdlog::details::os::is_color_terminal();
return;
case spdlog::color_mode::never:
default:
should_do_colors_ = false;
}
}

std::string BedrockLogSink::to_string(const spdlog::string_view_t &sv)
{
return {sv.data(), sv.size()};
}

void BedrockLogSink::sink_it_(const spdlog::details::log_msg &msg)
{
msg.color_range_start = 0;
msg.color_range_end = 0;
spdlog::memory_buf_t formatted;
formatter_->format(msg, formatted);
if (should_do_colors_ && msg.color_range_end > msg.color_range_start) {
// before color range
print_range_(formatted, 0, msg.color_range_start);
// in color range
print_ccode_(colors_.at(static_cast<size_t>(msg.level)));
print_range_(formatted, msg.color_range_start, msg.color_range_end);
print_ccode_(reset);
// after color range
print_range_(formatted, msg.color_range_end, formatted.size());
}
else // no color
{
print_range_(formatted, 0, formatted.size());
}
fflush(target_file_);
}

void BedrockLogSink::flush_()
{
fflush(target_file_);
}

void BedrockLogSink::print_ccode_(const spdlog::string_view_t &color_code)
{
fwrite(color_code.data(), sizeof(char), color_code.size(), target_file_);
}

void BedrockLogSink::print_range_(const spdlog::memory_buf_t &formatted, size_t start, size_t end)
{
fwrite(formatted.data() + start, sizeof(char), end - start, target_file_);
}
Loading

0 comments on commit 62ed16e

Please sign in to comment.