From f6cbd3663735f3ba678491bc0bb17a05a517af66 Mon Sep 17 00:00:00 2001 From: dentiny Date: Thu, 30 Jan 2025 22:40:09 +0000 Subject: [PATCH] fd sink Signed-off-by: dentiny --- src/ray/util/BUILD | 10 ++++ src/ray/util/spdlog_fd_sink.h | 69 +++++++++++++++++++++++ src/ray/util/tests/BUILD | 11 ++++ src/ray/util/tests/spdlog_fd_sink_test.cc | 57 +++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 src/ray/util/spdlog_fd_sink.h create mode 100644 src/ray/util/tests/spdlog_fd_sink_test.cc diff --git a/src/ray/util/BUILD b/src/ray/util/BUILD index 6d871ce4e846..3ce4089f651c 100644 --- a/src/ray/util/BUILD +++ b/src/ray/util/BUILD @@ -287,3 +287,13 @@ ray_cc_library( ":util", ], ) + +ray_cc_library( + name = "sodlog_fd_sink", + hdrs = ["spdlog_fd_sink.h"], + deps = [ + ":compat", + ":util", + "@com_github_spdlog//:spdlog", + ], +) diff --git a/src/ray/util/spdlog_fd_sink.h b/src/ray/util/spdlog_fd_sink.h new file mode 100644 index 000000000000..e7cd695d74d4 --- /dev/null +++ b/src/ray/util/spdlog_fd_sink.h @@ -0,0 +1,69 @@ +// Copyright 2025 The Ray Authors. +// +// 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 + +#include "ray/util/compat.h" +#include "ray/util/util.h" + +#if defined(__APPLE__) || defined(__linux__) +#include +#elif defined(_WIN32) +#include +#endif + +namespace spdlog::sinks { + +// A sink which logs to the file descriptor. +template +class fd_sink final : public base_sink { + public: + // [fd] is not owned by [FdSink], which means the file descriptor should be closed by + // caller. + explicit fd_sink(int fd) : fd_(fd) {} + + protected: + void sink_it_(const details::log_msg &msg) override { + memory_buf_t formatted; + base_sink::formatter_->format(msg, formatted); + +#if defined(__APPLE__) || defined(__linux__) + RAY_CHECK_EQ(write(fd_, formatted.data(), formatted.size()), formatted.size()) + << "Fails to write because " << strerror(errno); +#elif defined(_WIN32) + LPDWORD bytes_written; + BOOL success = + WriteFile(fd_, formatted.data(), (DWORD)formatted.size(), &bytes_written, NULL); + RAY_CHECK(success); + RAY_CHECK_EQ((LPDWORD)formatted.size(), bytes_written); +#endif + } + void flush_() override { +#if defined(__APPLE__) || defined(__linux__) + RAY_CHECK_EQ(close(fd_), 0) << "Fails to close file because " << strerror(errno); +#elif defined(_WIN32) + RAY_CHECK(CloseHandle(fd_)); +#endif + } + + private: + MEMFD_TYPE_NON_UNIQUE fd_; +}; + +using fd_sink_mt = fd_sink; +using fd_sink_st = fd_sink; + +} // namespace spdlog::sinks diff --git a/src/ray/util/tests/BUILD b/src/ray/util/tests/BUILD index 7e8608e90e19..7c1f4313206a 100644 --- a/src/ray/util/tests/BUILD +++ b/src/ray/util/tests/BUILD @@ -250,3 +250,14 @@ ray_cc_test( size = "small", tags = ["team:core"], ) + +ray_cc_test( + name = "spdlog_fd_sink_test", + srcs = ["spdlog_fd_sink_test.cc"], + deps = [ + "//src/ray/util:sodlog_fd_sink", + "@com_google_googletest//:gtest_main", + ], + size = "small", + tags = ["team:core"], +) diff --git a/src/ray/util/tests/spdlog_fd_sink_test.cc b/src/ray/util/tests/spdlog_fd_sink_test.cc new file mode 100644 index 000000000000..5a62ea96ad2f --- /dev/null +++ b/src/ray/util/tests/spdlog_fd_sink_test.cc @@ -0,0 +1,57 @@ +// Copyright 2025 The Ray Authors. +// +// 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 "ray/util/spdlog_fd_sink.h" + +#include + +namespace spdlog::sinks { + +namespace { + +#if defined(__APPLE__) || defined(__linux__) +int GetStdoutHandle() { return STDOUT_FILENO; } +#elif defined(_WIN32) +HANDLE GetStdoutHandle() { return GetStdHandle(STD_OUTPUT_HANDLE); } +#endif + +// Returns "helloworld" for whatever message; here we don't care the what message is +// logged, we only care whether msg has been written to the given file descriptor +// correctly. +class NoopFormatter : public formatter { + public: + void format(const details::log_msg &msg, memory_buf_t &dest) override { + dest.append(std::string{"helloworld"}); + } + std::unique_ptr clone() const override { + return std::make_unique(); + } +}; + +TEST(SpdlogFdSinkTest, SinkWithFd) { + fd_sink_st sink{GetStdoutHandle()}; + sink.set_formatter(std::make_unique()); + details::log_msg msg_to_log{ + /*logger_name=*/"logger_name", level::level_enum::info, /*msg=*/"content"}; + + testing::internal::CaptureStdout(); + sink.log(msg_to_log); + const std::string stdout_content = testing::internal::GetCapturedStdout(); + + EXPECT_EQ(stdout_content, "helloworld"); +}; + +} // namespace + +} // namespace spdlog::sinks