mirror of
https://github.com/SFML/SFML.git
synced 2024-11-28 14:21:04 +08:00
Add new thread-safe error logging API
This commit is contained in:
parent
7ba672139c
commit
4aee858fef
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
62
src/SFML/System/Logging.hpp
Normal file
62
src/SFML/System/Logging.hpp
Normal 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
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
" }");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user