Add new thread-safe error logging API

This commit is contained in:
Chris Thrasher 2024-07-10 14:01:59 -06:00
parent 7ba672139c
commit 4aee858fef
No known key found for this signature in database
GPG Key ID: 56FB686C9DFC8E2C
14 changed files with 210 additions and 49 deletions

View File

@ -40,6 +40,12 @@ namespace sf
////////////////////////////////////////////////////////////
[[nodiscard]] SFML_SYSTEM_API std::ostream& err();
////////////////////////////////////////////////////////////
/// \brief Specify buffer to which warnings and errors are logged
///
////////////////////////////////////////////////////////////
SFML_SYSTEM_API void setErrorBuffer(std::streambuf* streamBuffer);
} // namespace sf

View File

@ -12,6 +12,7 @@ set(SRC
${INCROOT}/Err.hpp
${INCROOT}/Export.hpp
${INCROOT}/InputStream.hpp
${SRCROOT}/Logging.hpp
${INCROOT}/NativeActivity.hpp
${SRCROOT}/Sleep.cpp
${INCROOT}/Sleep.hpp

View File

@ -26,8 +26,10 @@
// Headers
////////////////////////////////////////////////////////////
#include <SFML/System/Err.hpp>
#include <SFML/System/Logging.hpp>
#include <iostream>
#include <mutex>
#include <streambuf>
#include <cstdio>
@ -96,6 +98,25 @@ private:
namespace sf
{
namespace priv
{
////////////////////////////////////////////////////////////
std::mutex& errorStreamMutex()
{
static std::mutex mutex;
return mutex;
}
////////////////////////////////////////////////////////////
std::ostream& errorStream()
{
static std::ostream errorStream(std::cerr.rdbuf());
return errorStream;
}
} // namespace priv
////////////////////////////////////////////////////////////
std::ostream& err()
{
@ -106,4 +127,12 @@ std::ostream& err()
}
////////////////////////////////////////////////////////////
void setErrorBuffer(std::streambuf* streamBuffer)
{
const std::lock_guard lock(priv::errorStreamMutex());
priv::errorStream().rdbuf(streamBuffer);
}
} // namespace sf

View File

@ -0,0 +1,62 @@
////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2024 Laurent Gomila (laurent@sfml-dev.org)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
#pragma once
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/System/Export.hpp>
#include <mutex>
#include <ostream>
namespace sf::priv
{
////////////////////////////////////////////////////////////
/// \brief Get mutex for locking error stream
///
////////////////////////////////////////////////////////////
SFML_SYSTEM_API std::mutex& errorStreamMutex();
////////////////////////////////////////////////////////////
/// \brief Get error stream
///
////////////////////////////////////////////////////////////
SFML_SYSTEM_API std::ostream& errorStream();
////////////////////////////////////////////////////////////
/// \brief Log warning or error to a given stream
///
////////////////////////////////////////////////////////////
template <typename... Args>
void log(Args&&... args)
{
const std::lock_guard lock(errorStreamMutex());
(errorStream() << ... << std::forward<Args>(args));
errorStream() << std::endl;
}
} // namespace sf::priv

View File

@ -28,7 +28,7 @@
#include <SFML/Window/Context.hpp>
#include <SFML/Window/GlContext.hpp>
#include <SFML/System/Err.hpp>
#include <SFML/System/Logging.hpp>
#include <ostream>
#include <utility>
@ -50,7 +50,7 @@ namespace sf
Context::Context() : m_context(priv::GlContext::create())
{
if (!setActive(true))
err() << "Failed to set context as active during construction" << std::endl;
priv::log("Failed to set context as active during construction");
}
@ -58,7 +58,7 @@ Context::Context() : m_context(priv::GlContext::create())
Context::~Context()
{
if (m_context && !setActive(false))
err() << "Failed to set context as inactive during destruction" << std::endl;
priv::log("Failed to set context as inactive during destruction");
}
@ -144,7 +144,7 @@ Context::Context(const ContextSettings& settings, const Vector2u& size) :
m_context(priv::GlContext::create(settings, size))
{
if (!setActive(true))
err() << "Failed to set context as active during construction" << std::endl;
priv::log("Failed to set context as active during construction");
}
} // namespace sf

View File

@ -28,7 +28,7 @@
#include <SFML/Window/Cursor.hpp>
#include <SFML/Window/CursorImpl.hpp>
#include <SFML/System/Err.hpp>
#include <SFML/System/Logging.hpp>
#include <SFML/System/Vector2.hpp>
#include <memory>
@ -60,7 +60,7 @@ std::optional<Cursor> Cursor::loadFromPixels(const std::uint8_t* pixels, Vector2
{
if ((pixels == nullptr) || (size.x == 0) || (size.y == 0))
{
err() << "Failed to load cursor from pixels (invalid arguments)" << std::endl;
priv::log("Failed to load cursor from pixels (invalid arguments)");
return std::nullopt;
}

View File

@ -28,7 +28,7 @@
////////////////////////////////////////////////////////////
#include <SFML/Window/EGLCheck.hpp>
#include <SFML/System/Err.hpp>
#include <SFML/System/Logging.hpp>
#include <glad/egl.h>
@ -163,9 +163,17 @@ void eglCheckError(const std::filesystem::path& file, unsigned int line, std::st
}
// Log the error
err() << "An internal EGL call failed in " << file.filename() << " (" << line << ") : "
<< "\nExpression:\n " << expression << "\nError description:\n " << error << "\n " << description << '\n'
<< std::endl;
priv::log("An internal EGL call failed in ",
file.filename(),
" (",
line,
") : ",
"\nExpression:\n ",
expression,
"\nError description:\n ",
error,
"\n ",
description);
}
}

View File

@ -29,7 +29,7 @@
#include <SFML/Window/EglContext.hpp>
#include <SFML/Window/WindowImpl.hpp>
#include <SFML/System/Err.hpp>
#include <SFML/System/Logging.hpp>
#include <SFML/System/Sleep.hpp>
#include <memory>
@ -93,7 +93,7 @@ void ensureInit()
{
// At this point, the failure is unrecoverable
// Dump a message to the console and let the application terminate
sf::err() << "Failed to load EGL entry points" << std::endl;
sf::priv::log("Failed to load EGL entry points");
assert(false);
@ -177,9 +177,9 @@ EglContext::EglContext(EglContext* /*shared*/, const ContextSettings& /*settings
{
EglContextImpl::ensureInit();
sf::err() << "Warning: context has not been initialized. The constructor EglContext(shared, settings, size) is "
"currently not implemented."
<< std::endl;
sf::priv::log(
"Warning: context has not been initialized. The constructor EglContext(shared, settings, size) is "
"currently not implemented.");
}
@ -424,7 +424,7 @@ XVisualInfo EglContext::selectBestVisual(::Display* xDisplay, unsigned int bitsP
if (nativeVisualId == 0)
{
// Should never happen...
err() << "No EGL visual found. You should check your graphics driver" << std::endl;
priv::log("No EGL visual found. You should check your graphics driver");
return {};
}
@ -439,7 +439,7 @@ XVisualInfo EglContext::selectBestVisual(::Display* xDisplay, unsigned int bitsP
if (visualCount == 0)
{
// Can't happen...
err() << "No X11 visual found. Bug in your EGL implementation ?" << std::endl;
priv::log("No X11 visual found. Bug in your EGL implementation ?");
return {};
}

View File

@ -29,7 +29,7 @@
#include <SFML/Window/ContextSettings.hpp>
#include <SFML/Window/GlContext.hpp>
#include <SFML/System/Err.hpp>
#include <SFML/System/Logging.hpp>
#include <glad/gl.h>
@ -897,7 +897,7 @@ void GlContext::initialize(const ContextSettings& requestedSettings)
if (!glGetIntegervFunc || !glGetErrorFunc || !glGetStringFunc || !glEnableFunc || !glIsEnabledFunc)
{
err() << "Could not load necessary function to initialize OpenGL context" << std::endl;
log("Could not load necessary function to initialize OpenGL context");
return;
}
@ -949,13 +949,12 @@ void GlContext::initialize(const ContextSettings& requestedSettings)
!parseVersionString(version, "OpenGL ES ", m_settings.majorVersion, m_settings.minorVersion) &&
!parseVersionString(version, "", m_settings.majorVersion, m_settings.minorVersion))
{
err() << "Unable to parse OpenGL version string: " << std::quoted(version) << ", defaulting to 1.1"
<< std::endl;
log("Unable to parse OpenGL version string: ", std::quoted(version), ", defaulting to 1.1");
}
}
else
{
err() << "Unable to retrieve OpenGL version string, defaulting to 1.1" << std::endl;
log("Unable to retrieve OpenGL version string, defaulting to 1.1");
}
}
@ -1038,7 +1037,7 @@ void GlContext::initialize(const ContextSettings& requestedSettings)
// Check to see if the enable was successful
if (glIsEnabledFunc(GL_FRAMEBUFFER_SRGB) == GL_FALSE)
{
err() << "Warning: Failed to enable GL_FRAMEBUFFER_SRGB" << std::endl;
log("Warning: Failed to enable GL_FRAMEBUFFER_SRGB");
m_settings.sRgbCapable = false;
}
}
@ -1061,19 +1060,44 @@ void GlContext::checkSettings(const ContextSettings& requestedSettings) const
(m_settings.antialiasingLevel < requestedSettings.antialiasingLevel) ||
(m_settings.depthBits < requestedSettings.depthBits) || (!m_settings.sRgbCapable && requestedSettings.sRgbCapable))
{
err() << "Warning: The created OpenGL context does not fully meet the settings that were requested" << '\n'
<< "Requested: version = " << requestedSettings.majorVersion << "." << requestedSettings.minorVersion
<< " ; depth bits = " << requestedSettings.depthBits << " ; stencil bits = " << requestedSettings.stencilBits
<< " ; AA level = " << requestedSettings.antialiasingLevel << std::boolalpha
<< " ; core = " << ((requestedSettings.attributeFlags & ContextSettings::Core) != 0)
<< " ; debug = " << ((requestedSettings.attributeFlags & ContextSettings::Debug) != 0)
<< " ; sRGB = " << requestedSettings.sRgbCapable << std::noboolalpha << '\n'
<< "Created: version = " << m_settings.majorVersion << "." << m_settings.minorVersion
<< " ; depth bits = " << m_settings.depthBits << " ; stencil bits = " << m_settings.stencilBits
<< " ; AA level = " << m_settings.antialiasingLevel << std::boolalpha
<< " ; core = " << ((m_settings.attributeFlags & ContextSettings::Core) != 0)
<< " ; debug = " << ((m_settings.attributeFlags & ContextSettings::Debug) != 0)
<< " ; sRGB = " << m_settings.sRgbCapable << std::noboolalpha << std::endl;
log("Warning: The created OpenGL context does not fully meet the settings that were requested\n",
"Requested: version = ",
requestedSettings.majorVersion,
".",
requestedSettings.minorVersion,
" ; depth bits = ",
requestedSettings.depthBits,
" ; stencil bits = ",
requestedSettings.stencilBits,
" ; AA level = ",
requestedSettings.antialiasingLevel,
std::boolalpha,
" ; core = ",
((requestedSettings.attributeFlags & ContextSettings::Core) != 0),
" ; debug = ",
((requestedSettings.attributeFlags & ContextSettings::Debug) != 0),
" ; sRGB = ",
requestedSettings.sRgbCapable,
std::noboolalpha,
'\n',
"Created: version = ",
m_settings.majorVersion,
".",
m_settings.minorVersion,
" ; depth bits = ",
m_settings.depthBits,
" ; stencil bits = ",
m_settings.stencilBits,
" ; AA level = ",
m_settings.antialiasingLevel,
std::boolalpha,
" ; core = ",
((m_settings.attributeFlags & ContextSettings::Core) != 0),
" ; debug = ",
((m_settings.attributeFlags & ContextSettings::Debug) != 0),
" ; sRGB = ",
m_settings.sRgbCapable,
std::noboolalpha);
}
}

View File

@ -27,7 +27,7 @@
////////////////////////////////////////////////////////////
#include <SFML/Window/SensorManager.hpp>
#include <SFML/System/Err.hpp>
#include <SFML/System/Logging.hpp>
#include <ostream>
@ -59,8 +59,7 @@ void SensorManager::setEnabled(Sensor::Type sensor, bool enabled)
}
else
{
err() << "Warning: trying to enable a sensor that is not available (call Sensor::isAvailable to check it)"
<< std::endl;
log("Warning: trying to enable a sensor that is not available (call Sensor::isAvailable to check it)");
}
}
@ -115,7 +114,7 @@ SensorManager::SensorManager()
else
{
m_sensors[sensor].available = false;
err() << "Warning: sensor " << i << " failed to open, will not be available" << std::endl;
log("Warning: sensor ", i, " failed to open, will not be available");
}
}
}

View File

@ -29,7 +29,7 @@
#include <SFML/Window/Window.hpp>
#include <SFML/Window/WindowImpl.hpp>
#include <SFML/System/Err.hpp>
#include <SFML/System/Logging.hpp>
#include <SFML/System/Sleep.hpp>
#include <ostream>
@ -164,7 +164,7 @@ bool Window::setActive(bool active) const
return true;
}
err() << "Failed to activate the window's context" << std::endl;
priv::log("Failed to activate the window's context");
return false;
}
@ -201,7 +201,7 @@ void Window::initialize()
// Activate the window
if (!setActive())
{
err() << "Failed to set window as active during initialization" << std::endl;
priv::log("Failed to set window as active during initialization");
}
WindowBase::initialize();

View File

@ -31,7 +31,7 @@
#include <SFML/Window/SensorManager.hpp>
#include <SFML/Window/WindowImpl.hpp>
#include <SFML/System/Err.hpp>
#include <SFML/System/Logging.hpp>
#include <SFML/System/Sleep.hpp>
#include <SFML/System/Time.hpp>
@ -126,17 +126,22 @@ std::unique_ptr<WindowImpl> WindowImpl::create(
// Make sure there's not already a fullscreen window (only one is allowed)
if (WindowImplImpl::fullscreenWindow != nullptr)
{
err() << "Creating two fullscreen windows is not allowed, switching to windowed mode" << std::endl;
log("Creating two fullscreen windows is not allowed, switching to windowed mode");
state = State::Windowed;
}
// Make sure that the chosen video mode is compatible
else if (!mode.isValid())
{
err() << "The requested video mode is not available, switching to a valid mode" << std::endl;
log("The requested video mode is not available, switching to a valid mode");
assert(!VideoMode::getFullscreenModes().empty() && "No video modes available");
mode = VideoMode::getFullscreenModes()[0];
err() << " VideoMode: { size: { " << mode.size.x << ", " << mode.size.y
<< " }, bitsPerPixel: " << mode.bitsPerPixel << " }" << std::endl;
log(" VideoMode: { size: { ",
mode.size.x,
", ",
mode.size.y,
" }, bitsPerPixel: ",
mode.bitsPerPixel,
" }");
}
}

View File

@ -54,7 +54,6 @@ set(SYSTEM_SRC
System/Angle.test.cpp
System/Clock.test.cpp
System/Config.test.cpp
System/Err.test.cpp
System/FileInputStream.test.cpp
System/MemoryInputStream.test.cpp
System/Sleep.test.cpp
@ -76,6 +75,7 @@ set(WINDOW_SRC
Window/Context.test.cpp
Window/ContextSettings.test.cpp
Window/Cursor.test.cpp
Window/Err.test.cpp # Tested alongside Window module because the System module performs no logging
Window/Event.test.cpp
Window/GlResource.test.cpp
Window/Joystick.test.cpp

View File

@ -1,8 +1,13 @@
#include <SFML/System/Err.hpp>
// Other 1st party headers
#include <SFML/Window/Cursor.hpp>
#include <catch2/catch_test_macros.hpp>
#include <array>
#include <sstream>
#include <thread>
TEST_CASE("[System] sf::err")
{
@ -38,4 +43,26 @@ TEST_CASE("[System] sf::err")
sf::err().rdbuf(defaultStreamBuffer);
CHECK(sf::err().rdbuf() == defaultStreamBuffer);
}
SECTION("Log errors")
{
using namespace std::string_literals;
using namespace std::string_view_literals;
const std::stringstream stream;
sf::setErrorBuffer(stream.rdbuf());
// Produce logs concurrently from multiple threads
std::array threads{std::thread([] { (void)sf::Cursor::loadFromPixels(nullptr, {}, {}); }),
std::thread([] { (void)sf::Cursor::loadFromPixels(nullptr, {}, {}); }),
std::thread([] { (void)sf::Cursor::loadFromPixels(nullptr, {}, {}); })};
for (auto& thread : threads)
thread.join();
// Ensure all messages come through in their entirety without being interleaved
CHECK(stream.str() ==
"Failed to load cursor from pixels (invalid arguments)\n"
"Failed to load cursor from pixels (invalid arguments)\n"
"Failed to load cursor from pixels (invalid arguments)\n");
}
}