Compare commits
50 Commits
feature/no
...
feature/se
Author | SHA1 | Date | |
---|---|---|---|
1ac02fdc0e | |||
687956358d | |||
1a42c92325 | |||
6bb00b6788 | |||
12f6cd878d | |||
9aacebbbaf | |||
701c3745c2 | |||
156288b17b | |||
ed0e23e8a5 | |||
4c4f99606e | |||
a61586c846 | |||
d64d50c978 | |||
a64b7b0c4a | |||
0caeb81327 | |||
edac7a0171 | |||
abfadad2e9 | |||
2dc1547bbd | |||
5eb23c9764 | |||
9f4b2856b0 | |||
b5fc10326e | |||
8d3a47a873 | |||
4df58f3059 | |||
06b8cb8d3b | |||
ff81f5b496 | |||
c89f73006e | |||
c28951f049 | |||
dfaaaca223 | |||
c7f0bf3d64 | |||
234ce4c173 | |||
f60293b2e7 | |||
9441095637 | |||
f82d38f758 | |||
a7f42f35db | |||
cb1d1bfd85 | |||
28c3f2ea26 | |||
8dc132dbd3 | |||
98e2fbca6a | |||
fa7f0fadde | |||
2732dfd0f1 | |||
2e4c4b72b6 | |||
fc21ad519b | |||
c65cfd3d26 | |||
8955462f73 | |||
205c8c15bd | |||
78198a0147 | |||
d561e1141e | |||
753fc845ac | |||
5dbc00bbfe | |||
14ec8522ef | |||
0c2d1c22bc |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
build
|
||||
*.pyc
|
||||
|
@ -37,6 +37,7 @@ set( IXWEBSOCKET_SOURCES
|
||||
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
||||
ixwebsocket/IXHttpClient.cpp
|
||||
ixwebsocket/IXUrlParser.cpp
|
||||
ixwebsocket/LUrlParser.cpp
|
||||
ixwebsocket/IXSelectInterrupt.cpp
|
||||
ixwebsocket/IXSelectInterruptFactory.cpp
|
||||
ixwebsocket/IXConnectionState.cpp
|
||||
@ -65,6 +66,7 @@ set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/libwshandshake.hpp
|
||||
ixwebsocket/IXHttpClient.h
|
||||
ixwebsocket/IXUrlParser.h
|
||||
ixwebsocket/LUrlParser.h
|
||||
ixwebsocket/IXSelectInterrupt.h
|
||||
ixwebsocket/IXSelectInterruptFactory.h
|
||||
ixwebsocket/IXConnectionState.h
|
||||
|
@ -1 +1 @@
|
||||
1.4.3
|
||||
2.0.0
|
||||
|
@ -1 +1 @@
|
||||
docker/Dockerfile.fedora
|
||||
docker/Dockerfile.ubuntu_xenial
|
31
README.md
31
README.md
@ -10,8 +10,7 @@
|
||||
* iOS
|
||||
* Linux
|
||||
* Android
|
||||
|
||||
The code was made to compile once on Windows but support is currently broken on this platform.
|
||||
* Windows (no TLS)
|
||||
|
||||
## Examples
|
||||
|
||||
@ -38,7 +37,7 @@ webSocket.setOnMessageCallback(
|
||||
const ix::WebSocketOpenInfo& openInfo,
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
if (messageType == ix::WebSocket_MessageType_Message)
|
||||
if (messageType == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
std::cout << str << std::endl;
|
||||
}
|
||||
@ -78,7 +77,7 @@ server.setOnConnectionCallback(
|
||||
const ix::WebSocketOpenInfo& openInfo,
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
if (messageType == ix::WebSocket_MessageType_Open)
|
||||
if (messageType == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
std::cerr << "New connection" << std::endl;
|
||||
|
||||
@ -97,7 +96,7 @@ server.setOnConnectionCallback(
|
||||
std::cerr << it.first << ": " << it.second << std::endl;
|
||||
}
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
||||
else if (messageType == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
// For an echo server, we just send back to the client whatever was received by the server
|
||||
// All connected clients are available in an std::set. See the broadcast cpp example.
|
||||
@ -199,6 +198,8 @@ make install # will install to /usr/local on Unix, on macOS it is a good idea to
|
||||
|
||||
Headers and a static library will be installed to the target dir.
|
||||
|
||||
A [conan](https://conan.io/) file is available at [conan-IXWebSocket](https://github.com/Zinnion/conan-IXWebSocket).
|
||||
|
||||
There is a unittest which can be executed by typing `make test`.
|
||||
|
||||
There is a Dockerfile for running some code on Linux. To use docker-compose you must make a docker container first.
|
||||
@ -300,10 +301,10 @@ If the connection was closed and sending failed, the return value will be set to
|
||||
|
||||
`getReadyState()` returns the state of the connection. There are 4 possible states.
|
||||
|
||||
1. WebSocket_ReadyState_Connecting - The connection is not yet open.
|
||||
2. WebSocket_ReadyState_Open - The connection is open and ready to communicate.
|
||||
3. WebSocket_ReadyState_Closing - The connection is in the process of closing.
|
||||
4. WebSocket_MessageType_Close - The connection is closed or could not be opened.
|
||||
1. ReadyState::Connecting - The connection is not yet open.
|
||||
2. ReadyState::Open - The connection is open and ready to communicate.
|
||||
3. ReadyState::Closing - The connection is in the process of closing.
|
||||
4. ReadyState::Closed - The connection is closed or could not be opened.
|
||||
|
||||
### Open and Close notifications
|
||||
|
||||
@ -318,7 +319,7 @@ webSocket.setOnMessageCallback(
|
||||
const ix::WebSocketOpenInfo& openInfo,
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
if (messageType == ix::WebSocket_MessageType_Open)
|
||||
if (messageType == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
std::cout << "send greetings" << std::endl;
|
||||
|
||||
@ -329,7 +330,7 @@ webSocket.setOnMessageCallback(
|
||||
std::cout << it.first << ": " << it.second << std::endl;
|
||||
}
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||
else if (messageType == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
std::cout << "disconnected" << std::endl;
|
||||
|
||||
@ -344,7 +345,7 @@ webSocket.setOnMessageCallback(
|
||||
|
||||
### Error notification
|
||||
|
||||
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocket_MessageType_Error`. Multiple fields will be available on the event to describe the error.
|
||||
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocketMessageType::Error`. Multiple fields will be available on the event to describe the error.
|
||||
|
||||
```
|
||||
webSocket.setOnMessageCallback(
|
||||
@ -355,7 +356,7 @@ webSocket.setOnMessageCallback(
|
||||
const ix::WebSocketOpenInfo& openInfo,
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
if (messageType == ix::WebSocket_MessageType_Error)
|
||||
if (messageType == ix::WebSocketMessageType::Error)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Error: " << error.reason << std::endl;
|
||||
@ -395,8 +396,8 @@ webSocket.setOnMessageCallback(
|
||||
const ix::WebSocketOpenInfo& openInfo,
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
if (messageType == ix::WebSocket_MessageType_Ping ||
|
||||
messageType == ix::WebSocket_MessageType_Pong)
|
||||
if (messageType == ix::WebSocketMessageType::Ping ||
|
||||
messageType == ix::WebSocketMessageType::Pong)
|
||||
{
|
||||
std::cout << "pong data: " << str << std::endl;
|
||||
}
|
||||
|
@ -1,10 +1,14 @@
|
||||
image:
|
||||
- Visual Studio 2017
|
||||
- Ubuntu
|
||||
|
||||
install:
|
||||
- ls -al
|
||||
- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
|
||||
- python test/run.py
|
||||
- cd test
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -G"NMake Makefiles" ..
|
||||
- nmake
|
||||
- ixwebsocket_unittest.exe
|
||||
|
||||
build: off
|
||||
|
@ -35,7 +35,6 @@ namespace ix
|
||||
_activeJobs.erase(_id);
|
||||
}
|
||||
|
||||
// we want hostname to be copied, not passed as a const reference
|
||||
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
|
||||
int port,
|
||||
std::string& errMsg)
|
||||
|
@ -47,13 +47,12 @@ namespace ix
|
||||
|
||||
std::string protocol, host, path, query;
|
||||
int port;
|
||||
bool websocket = false;
|
||||
|
||||
if (!UrlParser::parse(url, protocol, host, path, query, port, websocket))
|
||||
if (!UrlParser::parse(url, protocol, host, path, query, port))
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Cannot parse url: " << url;
|
||||
return std::make_tuple(code, HttpErrorCode_UrlMalformed,
|
||||
return std::make_tuple(code, HttpErrorCode::UrlMalformed,
|
||||
headers, payload, ss.str(),
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -64,7 +63,7 @@ namespace ix
|
||||
|
||||
if (!_socket)
|
||||
{
|
||||
return std::make_tuple(code, HttpErrorCode_CannotCreateSocket,
|
||||
return std::make_tuple(code, HttpErrorCode::CannotCreateSocket,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -117,7 +116,7 @@ namespace ix
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Cannot connect to url: " << url;
|
||||
return std::make_tuple(code, HttpErrorCode_CannotConnect,
|
||||
return std::make_tuple(code, HttpErrorCode::CannotConnect,
|
||||
headers, payload, ss.str(),
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -143,7 +142,7 @@ namespace ix
|
||||
if (!_socket->writeBytes(req, isCancellationRequested))
|
||||
{
|
||||
std::string errorMsg("Cannot send request");
|
||||
return std::make_tuple(code, HttpErrorCode_SendError,
|
||||
return std::make_tuple(code, HttpErrorCode::SendError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -157,7 +156,7 @@ namespace ix
|
||||
if (!lineValid)
|
||||
{
|
||||
std::string errorMsg("Cannot retrieve status line");
|
||||
return std::make_tuple(code, HttpErrorCode_CannotReadStatusLine,
|
||||
return std::make_tuple(code, HttpErrorCode::CannotReadStatusLine,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -172,7 +171,7 @@ namespace ix
|
||||
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
|
||||
{
|
||||
std::string errorMsg("Cannot parse response code from status line");
|
||||
return std::make_tuple(code, HttpErrorCode_MissingStatus,
|
||||
return std::make_tuple(code, HttpErrorCode::MissingStatus,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -184,7 +183,7 @@ namespace ix
|
||||
if (!headersValid)
|
||||
{
|
||||
std::string errorMsg("Cannot parse http headers");
|
||||
return std::make_tuple(code, HttpErrorCode_HeaderParsingError,
|
||||
return std::make_tuple(code, HttpErrorCode::HeaderParsingError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -195,7 +194,7 @@ namespace ix
|
||||
if (headers.find("Location") == headers.end())
|
||||
{
|
||||
std::string errorMsg("Missing location header for redirect");
|
||||
return std::make_tuple(code, HttpErrorCode_MissingLocation,
|
||||
return std::make_tuple(code, HttpErrorCode::MissingLocation,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -204,7 +203,7 @@ namespace ix
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Too many redirects: " << redirects;
|
||||
return std::make_tuple(code, HttpErrorCode_TooManyRedirects,
|
||||
return std::make_tuple(code, HttpErrorCode::TooManyRedirects,
|
||||
headers, payload, ss.str(),
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -216,7 +215,7 @@ namespace ix
|
||||
|
||||
if (verb == "HEAD")
|
||||
{
|
||||
return std::make_tuple(code, HttpErrorCode_Ok,
|
||||
return std::make_tuple(code, HttpErrorCode::Ok,
|
||||
headers, payload, std::string(),
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -237,7 +236,7 @@ namespace ix
|
||||
if (!chunkResult.first)
|
||||
{
|
||||
errorMsg = "Cannot read chunk";
|
||||
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
||||
return std::make_tuple(code, HttpErrorCode::ChunkReadError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -255,7 +254,7 @@ namespace ix
|
||||
|
||||
if (!lineResult.first)
|
||||
{
|
||||
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
||||
return std::make_tuple(code, HttpErrorCode::ChunkReadError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -282,7 +281,7 @@ namespace ix
|
||||
if (!chunkResult.first)
|
||||
{
|
||||
errorMsg = "Cannot read chunk";
|
||||
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
||||
return std::make_tuple(code, HttpErrorCode::ChunkReadError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -293,7 +292,7 @@ namespace ix
|
||||
|
||||
if (!lineResult.first)
|
||||
{
|
||||
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
||||
return std::make_tuple(code, HttpErrorCode::ChunkReadError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -308,7 +307,7 @@ namespace ix
|
||||
else
|
||||
{
|
||||
std::string errorMsg("Cannot read http body");
|
||||
return std::make_tuple(code, HttpErrorCode_CannotReadBody,
|
||||
return std::make_tuple(code, HttpErrorCode::CannotReadBody,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -322,14 +321,14 @@ namespace ix
|
||||
if (!gzipInflate(payload, decompressedPayload))
|
||||
{
|
||||
std::string errorMsg("Error decompressing payload");
|
||||
return std::make_tuple(code, HttpErrorCode_Gzip,
|
||||
return std::make_tuple(code, HttpErrorCode::Gzip,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
payload = decompressedPayload;
|
||||
}
|
||||
|
||||
return std::make_tuple(code, HttpErrorCode_Ok,
|
||||
return std::make_tuple(code, HttpErrorCode::Ok,
|
||||
headers, payload, std::string(),
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
|
@ -19,23 +19,23 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
enum HttpErrorCode
|
||||
enum class HttpErrorCode
|
||||
{
|
||||
HttpErrorCode_Ok = 0,
|
||||
HttpErrorCode_CannotConnect = 1,
|
||||
HttpErrorCode_Timeout = 2,
|
||||
HttpErrorCode_Gzip = 3,
|
||||
HttpErrorCode_UrlMalformed = 4,
|
||||
HttpErrorCode_CannotCreateSocket = 5,
|
||||
HttpErrorCode_SendError = 6,
|
||||
HttpErrorCode_ReadError = 7,
|
||||
HttpErrorCode_CannotReadStatusLine = 8,
|
||||
HttpErrorCode_MissingStatus = 9,
|
||||
HttpErrorCode_HeaderParsingError = 10,
|
||||
HttpErrorCode_MissingLocation = 11,
|
||||
HttpErrorCode_TooManyRedirects = 12,
|
||||
HttpErrorCode_ChunkReadError = 13,
|
||||
HttpErrorCode_CannotReadBody = 14
|
||||
Ok = 0,
|
||||
CannotConnect = 1,
|
||||
Timeout = 2,
|
||||
Gzip = 3,
|
||||
UrlMalformed = 4,
|
||||
CannotCreateSocket = 5,
|
||||
SendError = 6,
|
||||
ReadError = 7,
|
||||
CannotReadStatusLine = 8,
|
||||
MissingStatus = 9,
|
||||
HeaderParsingError = 10,
|
||||
MissingLocation = 11,
|
||||
TooManyRedirects = 12,
|
||||
ChunkReadError = 13,
|
||||
CannotReadBody = 14
|
||||
};
|
||||
|
||||
using HttpResponse = std::tuple<int, // status
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* IXNetSystem.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Author: Korchynskyi Dmytro
|
||||
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||
*/
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class SelectInterruptEventFd : public SelectInterrupt {
|
||||
class SelectInterruptEventFd final : public SelectInterrupt {
|
||||
public:
|
||||
SelectInterruptEventFd();
|
||||
virtual ~SelectInterruptEventFd();
|
||||
|
@ -40,6 +40,8 @@ namespace ix
|
||||
|
||||
bool SelectInterruptPipe::init(std::string& errorMsg)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||
|
||||
// calling init twice is a programming error
|
||||
assert(_fildes[kPipeReadIndex] == -1);
|
||||
assert(_fildes[kPipeWriteIndex] == -1);
|
||||
@ -108,6 +110,8 @@ namespace ix
|
||||
|
||||
bool SelectInterruptPipe::notify(uint64_t value)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||
|
||||
int fd = _fildes[kPipeWriteIndex];
|
||||
if (fd == -1) return false;
|
||||
|
||||
@ -118,6 +122,8 @@ namespace ix
|
||||
// TODO: return max uint64_t for errors ?
|
||||
uint64_t SelectInterruptPipe::read()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||
|
||||
int fd = _fildes[kPipeReadIndex];
|
||||
|
||||
uint64_t value = 0;
|
||||
@ -133,6 +139,8 @@ namespace ix
|
||||
|
||||
int SelectInterruptPipe::getFd() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||
|
||||
return _fildes[kPipeReadIndex];
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,11 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class SelectInterruptPipe : public SelectInterrupt {
|
||||
class SelectInterruptPipe final : public SelectInterrupt {
|
||||
public:
|
||||
SelectInterruptPipe();
|
||||
virtual ~SelectInterruptPipe();
|
||||
@ -30,6 +31,7 @@ namespace ix
|
||||
// happens between a control thread and a background thread, which is
|
||||
// blocked on select.
|
||||
int _fildes[2];
|
||||
mutable std::mutex _fildesMutex;
|
||||
|
||||
// Used to identify the read/write idx
|
||||
static const int kPipeReadIndex;
|
||||
|
@ -19,7 +19,6 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
@ -45,14 +44,14 @@ namespace ix
|
||||
close();
|
||||
}
|
||||
|
||||
PollResultType Socket::poll(int timeoutSecs)
|
||||
PollResultType Socket::poll(int timeoutMs)
|
||||
{
|
||||
if (_sockfd == -1)
|
||||
{
|
||||
return PollResultType::Error;
|
||||
}
|
||||
|
||||
return isReadyToRead(1000 * timeoutSecs);
|
||||
return isReadyToRead(timeoutMs);
|
||||
}
|
||||
|
||||
PollResultType Socket::select(bool readyToRead, int timeoutMs)
|
||||
@ -74,7 +73,7 @@ namespace ix
|
||||
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = timeoutMs / 1000;
|
||||
timeout.tv_usec = (timeoutMs < 1000) ? 0 : 1000 * (timeoutMs % 1000);
|
||||
timeout.tv_usec = 1000 * (timeoutMs % 1000);
|
||||
|
||||
// Compute the highest fd.
|
||||
int sockfd = _sockfd;
|
||||
@ -160,8 +159,6 @@ namespace ix
|
||||
|
||||
ssize_t Socket::send(char* buffer, size_t length)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||
|
||||
int flags = 0;
|
||||
#ifdef MSG_NOSIGNAL
|
||||
flags = MSG_NOSIGNAL;
|
||||
@ -177,8 +174,6 @@ namespace ix
|
||||
|
||||
ssize_t Socket::recv(void* buffer, size_t length)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||
|
||||
int flags = 0;
|
||||
#ifdef MSG_NOSIGNAL
|
||||
flags = MSG_NOSIGNAL;
|
||||
@ -189,11 +184,27 @@ namespace ix
|
||||
|
||||
int Socket::getErrno()
|
||||
{
|
||||
int err;
|
||||
|
||||
#ifdef _WIN32
|
||||
return WSAGetLastError();
|
||||
err = WSAGetLastError();
|
||||
#else
|
||||
return errno;
|
||||
err = errno;
|
||||
#endif
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
bool Socket::isWaitNeeded()
|
||||
{
|
||||
int err = getErrno();
|
||||
|
||||
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Socket::closeSocket(int fd)
|
||||
@ -228,8 +239,7 @@ namespace ix
|
||||
return ret == len;
|
||||
}
|
||||
// There is possibly something to be writen, try again
|
||||
else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
|
||||
getErrno() == EAGAIN))
|
||||
else if (ret < 0 && Socket::isWaitNeeded())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -257,8 +267,7 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
// There is possibly something to be read, try again
|
||||
else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
|
||||
getErrno() == EAGAIN))
|
||||
else if (ret < 0 && Socket::isWaitNeeded())
|
||||
{
|
||||
// Wait with a 1ms timeout until the socket is ready to read.
|
||||
// This way we are not busy looping
|
||||
@ -317,13 +326,12 @@ namespace ix
|
||||
size_t size = std::min(kChunkSize, length - output.size());
|
||||
ssize_t ret = recv((char*)&_readBuffer[0], size);
|
||||
|
||||
if (ret <= 0 && (getErrno() != EWOULDBLOCK &&
|
||||
getErrno() != EAGAIN))
|
||||
if (ret <= 0 && !Socket::isWaitNeeded())
|
||||
{
|
||||
// Error
|
||||
return std::make_pair(false, std::string());
|
||||
}
|
||||
else if (ret > 0)
|
||||
else
|
||||
{
|
||||
output.insert(output.end(),
|
||||
_readBuffer.begin(),
|
||||
|
@ -16,6 +16,20 @@
|
||||
#ifdef _WIN32
|
||||
#include <BaseTsd.h>
|
||||
typedef SSIZE_T ssize_t;
|
||||
|
||||
#undef EWOULDBLOCK
|
||||
#undef EAGAIN
|
||||
#undef EINPROGRESS
|
||||
#undef EBADF
|
||||
#undef EINVAL
|
||||
|
||||
// map to WSA error codes
|
||||
#define EWOULDBLOCK WSAEWOULDBLOCK
|
||||
#define EAGAIN WSATRY_AGAIN
|
||||
#define EINPROGRESS WSAEINPROGRESS
|
||||
#define EBADF WSAEBADF
|
||||
#define EINVAL WSAEINVAL
|
||||
|
||||
#endif
|
||||
|
||||
#include "IXCancellationRequest.h"
|
||||
@ -27,12 +41,12 @@ namespace ix
|
||||
|
||||
enum class PollResultType
|
||||
{
|
||||
ReadyForRead = 0,
|
||||
ReadyForWrite = 1,
|
||||
Timeout = 2,
|
||||
Error = 3,
|
||||
SendRequest = 4,
|
||||
CloseRequest = 5
|
||||
ReadyForRead = 0,
|
||||
ReadyForWrite = 1,
|
||||
Timeout = 2,
|
||||
Error = 3,
|
||||
SendRequest = 4,
|
||||
CloseRequest = 5
|
||||
};
|
||||
|
||||
class Socket {
|
||||
@ -41,10 +55,8 @@ namespace ix
|
||||
virtual ~Socket();
|
||||
bool init(std::string& errorMsg);
|
||||
|
||||
void configure();
|
||||
|
||||
// Functions to check whether there is activity on the socket
|
||||
PollResultType poll(int timeoutSecs = kDefaultPollTimeout);
|
||||
PollResultType poll(int timeoutMs = kDefaultPollTimeout);
|
||||
bool wakeUpFromPoll(uint8_t wakeUpCode);
|
||||
|
||||
PollResultType isReadyToWrite(int timeoutMs);
|
||||
@ -76,14 +88,14 @@ namespace ix
|
||||
const CancellationRequest& isCancellationRequested);
|
||||
|
||||
static int getErrno();
|
||||
static bool isWaitNeeded();
|
||||
static void closeSocket(int fd);
|
||||
|
||||
// Used as special codes for pipe communication
|
||||
static const uint64_t kSendRequest;
|
||||
static const uint64_t kCloseRequest;
|
||||
|
||||
protected:
|
||||
void closeSocket(int fd);
|
||||
|
||||
std::atomic<int> _sockfd;
|
||||
std::mutex _socketMutex;
|
||||
|
||||
|
@ -20,8 +20,6 @@
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <errno.h>
|
||||
#define socketerrno errno
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class SocketAppleSSL : public Socket
|
||||
class SocketAppleSSL final : public Socket
|
||||
{
|
||||
public:
|
||||
SocketAppleSSL(int fd = -1);
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXDNSLookup.h"
|
||||
#include "IXNetSystem.h"
|
||||
#include "IXSocket.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
@ -18,18 +19,6 @@
|
||||
# include <linux/tcp.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
void closeSocket(int fd)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
closesocket(fd);
|
||||
#else
|
||||
::close(fd);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
namespace ix
|
||||
{
|
||||
//
|
||||
@ -56,11 +45,12 @@ namespace ix
|
||||
// block us for too long
|
||||
SocketConnect::configure(fd);
|
||||
|
||||
if (::connect(fd, address->ai_addr, address->ai_addrlen) == -1
|
||||
&& errno != EINPROGRESS && errno != 0)
|
||||
int res = ::connect(fd, address->ai_addr, address->ai_addrlen);
|
||||
|
||||
if (res == -1 && !Socket::isWaitNeeded())
|
||||
{
|
||||
errMsg = strerror(errno);
|
||||
closeSocket(fd);
|
||||
errMsg = strerror(Socket::getErrno());
|
||||
Socket::closeSocket(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -68,15 +58,17 @@ namespace ix
|
||||
{
|
||||
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
|
||||
{
|
||||
closeSocket(fd);
|
||||
Socket::closeSocket(fd);
|
||||
errMsg = "Cancelled";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Use select to check the status of the new connection
|
||||
// On Linux the timeout needs to be re-initialized everytime
|
||||
// http://man7.org/linux/man-pages/man2/select.2.html
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 10 * 1000; // 10ms timeout
|
||||
|
||||
fd_set wfds;
|
||||
fd_set efds;
|
||||
|
||||
@ -85,11 +77,13 @@ namespace ix
|
||||
FD_ZERO(&efds);
|
||||
FD_SET(fd, &efds);
|
||||
|
||||
if (select(fd + 1, nullptr, &wfds, &efds, &timeout) < 0 &&
|
||||
(errno == EBADF || errno == EINVAL))
|
||||
// Use select to check the status of the new connection
|
||||
res = select(fd + 1, nullptr, &wfds, &efds, &timeout);
|
||||
|
||||
if (res < 0 && (Socket::getErrno() == EBADF || Socket::getErrno() == EINVAL))
|
||||
{
|
||||
closeSocket(fd);
|
||||
errMsg = std::string("Connect error, select error: ") + strerror(errno);
|
||||
Socket::closeSocket(fd);
|
||||
errMsg = std::string("Connect error, select error: ") + strerror(Socket::getErrno());
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -110,7 +104,7 @@ namespace ix
|
||||
optval != 0)
|
||||
#endif
|
||||
{
|
||||
closeSocket(fd);
|
||||
Socket::closeSocket(fd);
|
||||
errMsg = strerror(optval);
|
||||
return -1;
|
||||
}
|
||||
@ -121,7 +115,7 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
closeSocket(fd);
|
||||
Socket::closeSocket(fd);
|
||||
errMsg = "connect timed out after 60 seconds";
|
||||
return -1;
|
||||
}
|
||||
|
@ -10,7 +10,6 @@
|
||||
#include "IXSocketConnect.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class SocketOpenSSL : public Socket
|
||||
class SocketOpenSSL final : public Socket
|
||||
{
|
||||
public:
|
||||
SocketOpenSSL(int fd = -1);
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class SocketSChannel : public Socket
|
||||
class SocketSChannel final : public Socket
|
||||
{
|
||||
public:
|
||||
SocketSChannel();
|
||||
|
@ -30,7 +30,9 @@ namespace ix
|
||||
_host(host),
|
||||
_backlog(backlog),
|
||||
_maxConnections(maxConnections),
|
||||
_serverFd(-1),
|
||||
_stop(false),
|
||||
_stopGc(false),
|
||||
_connectionStateFactory(&ConnectionState::createConnectionState)
|
||||
{
|
||||
|
||||
@ -77,7 +79,7 @@ namespace ix
|
||||
<< "at address " << _host << ":" << _port
|
||||
<< " : " << strerror(Socket::getErrno());
|
||||
|
||||
::close(_serverFd);
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
@ -101,7 +103,7 @@ namespace ix
|
||||
<< "at address " << _host << ":" << _port
|
||||
<< " : " << strerror(Socket::getErrno());
|
||||
|
||||
::close(_serverFd);
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
@ -115,7 +117,7 @@ namespace ix
|
||||
<< "at address " << _host << ":" << _port
|
||||
<< " : " << strerror(Socket::getErrno());
|
||||
|
||||
::close(_serverFd);
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
@ -124,9 +126,15 @@ namespace ix
|
||||
|
||||
void SocketServer::start()
|
||||
{
|
||||
if (_thread.joinable()) return; // we've already been started
|
||||
if (!_thread.joinable())
|
||||
{
|
||||
_thread = std::thread(&SocketServer::run, this);
|
||||
}
|
||||
|
||||
_thread = std::thread(&SocketServer::run, this);
|
||||
if (!_gcThread.joinable())
|
||||
{
|
||||
_gcThread = std::thread(&SocketServer::runGC, this);
|
||||
}
|
||||
}
|
||||
|
||||
void SocketServer::wait()
|
||||
@ -142,24 +150,24 @@ namespace ix
|
||||
|
||||
void SocketServer::stop()
|
||||
{
|
||||
while (true)
|
||||
// Stop accepting connections, and close the 'accept' thread
|
||||
if (_thread.joinable())
|
||||
{
|
||||
if (closeTerminatedThreads()) break;
|
||||
|
||||
// wait 10ms and try again later.
|
||||
// we could have a timeout, but if we exit of here
|
||||
// we leaked threads, it is quite bad.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
_stop = true;
|
||||
_thread.join();
|
||||
_stop = false;
|
||||
}
|
||||
|
||||
if (!_thread.joinable()) return; // nothing to do
|
||||
|
||||
_stop = true;
|
||||
_thread.join();
|
||||
_stop = false;
|
||||
// Join all threads and make sure that all connections are terminated
|
||||
if (_gcThread.joinable())
|
||||
{
|
||||
_stopGc = true;
|
||||
_gcThread.join();
|
||||
_stopGc = false;
|
||||
}
|
||||
|
||||
_conditionVariable.notify_one();
|
||||
::close(_serverFd);
|
||||
Socket::closeSocket(_serverFd);
|
||||
}
|
||||
|
||||
void SocketServer::setConnectionStateFactory(
|
||||
@ -175,7 +183,7 @@ namespace ix
|
||||
// field becomes true, and we can use that to know that we can join that thread
|
||||
// and remove it from our _connectionsThreads data structure (a list).
|
||||
//
|
||||
bool SocketServer::closeTerminatedThreads()
|
||||
void SocketServer::closeTerminatedThreads()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||
auto it = _connectionsThreads.begin();
|
||||
@ -195,8 +203,6 @@ namespace ix
|
||||
if (thread.joinable()) thread.join();
|
||||
it = _connectionsThreads.erase(it);
|
||||
}
|
||||
|
||||
return _connectionsThreads.empty();
|
||||
}
|
||||
|
||||
void SocketServer::run()
|
||||
@ -208,12 +214,6 @@ namespace ix
|
||||
{
|
||||
if (_stop) return;
|
||||
|
||||
// Garbage collection to shutdown/join threads for closed connections.
|
||||
// We could run this in its own thread, so that we dont need to accept
|
||||
// a new connection to close a thread.
|
||||
// We could also use a condition variable to be notify when we need to do this
|
||||
closeTerminatedThreads();
|
||||
|
||||
// Use select to check whether a new connection is in progress
|
||||
fd_set rfds;
|
||||
struct timeval timeout;
|
||||
@ -242,17 +242,18 @@ namespace ix
|
||||
// Accept a connection.
|
||||
struct sockaddr_in client; // client address information
|
||||
int clientFd; // socket connected to client
|
||||
socklen_t addressLen = sizeof(socklen_t);
|
||||
socklen_t addressLen = sizeof(client);
|
||||
memset(&client, 0, sizeof(client));
|
||||
|
||||
if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
|
||||
{
|
||||
if (Socket::getErrno() != EWOULDBLOCK)
|
||||
if (!Socket::isWaitNeeded())
|
||||
{
|
||||
// FIXME: that error should be propagated
|
||||
int err = Socket::getErrno();
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() error accepting connection: "
|
||||
<< strerror(Socket::getErrno());
|
||||
<< err << ", " << strerror(err);
|
||||
logError(ss.str());
|
||||
}
|
||||
continue;
|
||||
@ -266,7 +267,7 @@ namespace ix
|
||||
<< "Not accepting connection";
|
||||
logError(ss.str());
|
||||
|
||||
::close(clientFd);
|
||||
Socket::closeSocket(clientFd);
|
||||
|
||||
continue;
|
||||
}
|
||||
@ -280,7 +281,7 @@ namespace ix
|
||||
if (_stop) return;
|
||||
|
||||
// Launch the handleConnection work asynchronously in its own thread.
|
||||
std::lock_guard<std::mutex> lock(_conditionVariableMutex);
|
||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||
_connectionsThreads.push_back(std::make_pair(
|
||||
connectionState,
|
||||
std::thread(&SocketServer::handleConnection,
|
||||
@ -289,5 +290,30 @@ namespace ix
|
||||
connectionState)));
|
||||
}
|
||||
}
|
||||
|
||||
size_t SocketServer::getConnectionsThreadsCount()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||
return _connectionsThreads.size();
|
||||
}
|
||||
|
||||
void SocketServer::runGC()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
// Garbage collection to shutdown/join threads for closed connections.
|
||||
closeTerminatedThreads();
|
||||
|
||||
// We quit this thread if all connections are closed and we received
|
||||
// a stop request by setting _stopGc to true.
|
||||
if (_stopGc && getConnectionsThreadsCount() == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Sleep a little bit then keep cleaning up
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,6 +74,12 @@ namespace ix
|
||||
// background thread to wait for incoming connections
|
||||
std::atomic<bool> _stop;
|
||||
std::thread _thread;
|
||||
void run();
|
||||
|
||||
// background thread to cleanup (join) terminated threads
|
||||
std::atomic<bool> _stopGc;
|
||||
std::thread _gcThread;
|
||||
void runGC();
|
||||
|
||||
// the list of (connectionState, threads) for each connections
|
||||
ConnectionThreads _connectionsThreads;
|
||||
@ -87,13 +93,12 @@ namespace ix
|
||||
// the factory to create ConnectionState objects
|
||||
ConnectionStateFactory _connectionStateFactory;
|
||||
|
||||
// Methods
|
||||
void run();
|
||||
virtual void handleConnection(int fd,
|
||||
std::shared_ptr<ConnectionState> connectionState) = 0;
|
||||
virtual size_t getConnectedClientsCount() = 0;
|
||||
|
||||
// Returns true if all connection threads are joined
|
||||
bool closeTerminatedThreads();
|
||||
void closeTerminatedThreads();
|
||||
size_t getConnectionsThreadsCount();
|
||||
};
|
||||
}
|
||||
|
@ -5,43 +5,30 @@
|
||||
*/
|
||||
|
||||
#include "IXUrlParser.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "LUrlParser.h"
|
||||
|
||||
namespace ix
|
||||
{
|
||||
//
|
||||
// The only difference between those 2 regex is the protocol
|
||||
//
|
||||
std::regex UrlParser::_httpRegex("(http|https)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
|
||||
std::regex UrlParser::_webSocketRegex("(ws|wss)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
|
||||
|
||||
bool UrlParser::parse(const std::string& url,
|
||||
std::string& protocol,
|
||||
std::string& host,
|
||||
std::string& path,
|
||||
std::string& query,
|
||||
int& port,
|
||||
bool websocket)
|
||||
int& port)
|
||||
{
|
||||
std::cmatch what;
|
||||
if (!regex_match(url.c_str(), what,
|
||||
websocket ? _webSocketRegex : _httpRegex))
|
||||
LUrlParser::clParseURL res = LUrlParser::clParseURL::ParseURL(url);
|
||||
|
||||
if (!res.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string portStr;
|
||||
protocol = res.m_Scheme;
|
||||
host = res.m_Host;
|
||||
path = res.m_Path;
|
||||
query = res.m_Query;
|
||||
|
||||
protocol = std::string(what[1].first, what[1].second);
|
||||
host = std::string(what[2].first, what[2].second);
|
||||
portStr = std::string(what[3].first, what[3].second);
|
||||
path = std::string(what[4].first, what[4].second);
|
||||
query = std::string(what[5].first, what[5].second);
|
||||
|
||||
if (portStr.empty())
|
||||
if (!res.GetPort(&port))
|
||||
{
|
||||
if (protocol == "ws" || protocol == "http")
|
||||
{
|
||||
@ -58,12 +45,6 @@ namespace ix
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << portStr;
|
||||
ss >> port;
|
||||
}
|
||||
|
||||
if (path.empty())
|
||||
{
|
||||
@ -83,22 +64,4 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
void UrlParser::printUrl(const std::string& url, bool websocket)
|
||||
{
|
||||
std::string protocol, host, path, query;
|
||||
int port {0};
|
||||
|
||||
if (!parse(url, protocol, host, path, query, port, websocket))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "[" << url << "]" << std::endl;
|
||||
std::cout << protocol << std::endl;
|
||||
std::cout << host << std::endl;
|
||||
std::cout << port << std::endl;
|
||||
std::cout << path << std::endl;
|
||||
std::cout << query << std::endl;
|
||||
std::cout << "-------------------------------" << std::endl;
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <regex>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -19,13 +18,6 @@ namespace ix
|
||||
std::string& host,
|
||||
std::string& path,
|
||||
std::string& query,
|
||||
int& port,
|
||||
bool websocket);
|
||||
|
||||
static void printUrl(const std::string& url, bool websocket);
|
||||
|
||||
private:
|
||||
static std::regex _httpRegex;
|
||||
static std::regex _webSocketRegex;
|
||||
int& port);
|
||||
};
|
||||
}
|
||||
|
@ -8,22 +8,26 @@
|
||||
#include "IXSetThreadName.h"
|
||||
#include "IXWebSocketHandshake.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
|
||||
namespace
|
||||
{
|
||||
uint64_t calculateRetryWaitMilliseconds(uint64_t retry_count)
|
||||
uint64_t calculateRetryWaitMilliseconds(uint32_t retry_count)
|
||||
{
|
||||
// This will overflow quite fast for large value of retry_count
|
||||
// and will become 0, in which case the wait time will be none
|
||||
// and we'll be constantly retrying to connect.
|
||||
uint64_t wait_time = ((uint64_t) std::pow(2, retry_count) * 100L);
|
||||
uint64_t wait_time;
|
||||
|
||||
// cap the wait time to 10s, or to retry_count == 10 for which wait_time > 10s
|
||||
uint64_t tenSeconds = 10 * 1000;
|
||||
return (wait_time > tenSeconds || retry_count > 10) ? tenSeconds : wait_time;
|
||||
if (retry_count <= 6)
|
||||
{
|
||||
// max wait_time is 6400 ms (2 ^ 6 = 64)
|
||||
wait_time = ((uint64_t)std::pow(2, retry_count) * 100L);
|
||||
}
|
||||
else
|
||||
{
|
||||
wait_time = 10 * 1000; // 10 sec
|
||||
}
|
||||
|
||||
return wait_time;
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +42,6 @@ namespace ix
|
||||
WebSocket::WebSocket() :
|
||||
_onMessageCallback(OnMessageCallback()),
|
||||
_stop(false),
|
||||
_backgroundThreadRunning(false),
|
||||
_automaticReconnection(true),
|
||||
_handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs),
|
||||
_enablePong(kDefaultEnablePong),
|
||||
@ -48,7 +51,7 @@ namespace ix
|
||||
_ws.setOnCloseCallback(
|
||||
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote)
|
||||
{
|
||||
_onMessageCallback(WebSocket_MessageType_Close, "", wireSize,
|
||||
_onMessageCallback(WebSocketMessageType::Close, "", wireSize,
|
||||
WebSocketErrorInfo(), WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo(code, reason, remote));
|
||||
}
|
||||
@ -136,31 +139,21 @@ namespace ix
|
||||
{
|
||||
if (_thread.joinable()) return; // we've already been started
|
||||
|
||||
_backgroundThreadRunning = true;
|
||||
_thread = std::thread(&WebSocket::run, this);
|
||||
}
|
||||
|
||||
void WebSocket::stop()
|
||||
{
|
||||
bool automaticReconnection = _automaticReconnection;
|
||||
|
||||
// This value needs to be forced when shutting down, it is restored later
|
||||
_automaticReconnection = false;
|
||||
|
||||
close();
|
||||
|
||||
if (!_thread.joinable())
|
||||
if (_thread.joinable())
|
||||
{
|
||||
_automaticReconnection = automaticReconnection;
|
||||
return;
|
||||
// wait until working thread will exit
|
||||
// it will exit after close operation is finished
|
||||
_stop = true;
|
||||
_thread.join();
|
||||
_stop = false;
|
||||
}
|
||||
|
||||
_stop = true;
|
||||
_thread.join();
|
||||
_backgroundThreadRunning = false;
|
||||
_stop = false;
|
||||
|
||||
_automaticReconnection = automaticReconnection;
|
||||
}
|
||||
|
||||
WebSocketInitResult WebSocket::connect(int timeoutSecs)
|
||||
@ -179,7 +172,7 @@ namespace ix
|
||||
return status;
|
||||
}
|
||||
|
||||
_onMessageCallback(WebSocket_MessageType_Open, "", 0,
|
||||
_onMessageCallback(WebSocketMessageType::Open, "", 0,
|
||||
WebSocketErrorInfo(),
|
||||
WebSocketOpenInfo(status.uri, status.headers),
|
||||
WebSocketCloseInfo());
|
||||
@ -202,7 +195,7 @@ namespace ix
|
||||
return status;
|
||||
}
|
||||
|
||||
_onMessageCallback(WebSocket_MessageType_Open, "", 0,
|
||||
_onMessageCallback(WebSocketMessageType::Open, "", 0,
|
||||
WebSocketErrorInfo(),
|
||||
WebSocketOpenInfo(status.uri, status.headers),
|
||||
WebSocketCloseInfo());
|
||||
@ -211,49 +204,71 @@ namespace ix
|
||||
|
||||
bool WebSocket::isConnected() const
|
||||
{
|
||||
return getReadyState() == WebSocket_ReadyState_Open;
|
||||
return getReadyState() == ReadyState::Open;
|
||||
}
|
||||
|
||||
bool WebSocket::isClosing() const
|
||||
{
|
||||
return getReadyState() == WebSocket_ReadyState_Closing;
|
||||
return getReadyState() == ReadyState::Closing;
|
||||
}
|
||||
|
||||
void WebSocket::close()
|
||||
void WebSocket::close(uint16_t code,
|
||||
const std::string& reason)
|
||||
{
|
||||
_ws.close();
|
||||
_ws.close(code, reason);
|
||||
}
|
||||
|
||||
void WebSocket::reconnectPerpetuallyIfDisconnected()
|
||||
void WebSocket::checkConnection(bool firstConnectionAttempt)
|
||||
{
|
||||
uint64_t retries = 0;
|
||||
WebSocketErrorInfo connectErr;
|
||||
ix::WebSocketInitResult status;
|
||||
using millis = std::chrono::duration<double, std::milli>;
|
||||
|
||||
uint32_t retries = 0;
|
||||
millis duration;
|
||||
|
||||
// Try to connect perpertually
|
||||
while (true)
|
||||
{
|
||||
if (isConnected() || isClosing() || _stop || !_automaticReconnection)
|
||||
if (isConnected() || isClosing() || _stop)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
status = connect(_handshakeTimeoutSecs);
|
||||
|
||||
if (!status.success && !_stop)
|
||||
if (!firstConnectionAttempt && !_automaticReconnection)
|
||||
{
|
||||
duration = millis(calculateRetryWaitMilliseconds(retries++));
|
||||
// Do not attempt to reconnect
|
||||
break;
|
||||
}
|
||||
|
||||
connectErr.retries = retries;
|
||||
connectErr.wait_time = duration.count();
|
||||
connectErr.reason = status.errorStr;
|
||||
firstConnectionAttempt = false;
|
||||
|
||||
// Only sleep if we are retrying
|
||||
if (duration.count() > 0)
|
||||
{
|
||||
// to do: make sleeping conditional
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
|
||||
// Try to connect synchronously
|
||||
ix::WebSocketInitResult status = connect(_handshakeTimeoutSecs);
|
||||
|
||||
if (!status.success)
|
||||
{
|
||||
WebSocketErrorInfo connectErr;
|
||||
|
||||
if (_automaticReconnection)
|
||||
{
|
||||
duration = millis(calculateRetryWaitMilliseconds(retries++));
|
||||
|
||||
connectErr.wait_time = duration.count();
|
||||
connectErr.retries = retries;
|
||||
}
|
||||
|
||||
connectErr.reason = status.errorStr;
|
||||
connectErr.http_status = status.http_status;
|
||||
_onMessageCallback(WebSocket_MessageType_Error, "", 0,
|
||||
|
||||
_onMessageCallback(WebSocketMessageType::Error, "", 0,
|
||||
connectErr, WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo());
|
||||
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -262,22 +277,27 @@ namespace ix
|
||||
{
|
||||
setThreadName(getUrl());
|
||||
|
||||
bool firstConnectionAttempt = true;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (_stop) return;
|
||||
|
||||
// 1. Make sure we are always connected
|
||||
reconnectPerpetuallyIfDisconnected();
|
||||
checkConnection(firstConnectionAttempt);
|
||||
|
||||
if (_stop) return;
|
||||
firstConnectionAttempt = false;
|
||||
|
||||
// if here we are closed then checkConnection was not able to connect
|
||||
if (getReadyState() == ReadyState::Closed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// 2. Poll to see if there's any new data available
|
||||
_ws.poll();
|
||||
|
||||
if (_stop) return;
|
||||
WebSocketTransport::PollResult pollResult = _ws.poll();
|
||||
|
||||
// 3. Dispatch the incoming messages
|
||||
_ws.dispatch(
|
||||
pollResult,
|
||||
[this](const std::string& msg,
|
||||
size_t wireSize,
|
||||
bool decompressionError,
|
||||
@ -286,24 +306,25 @@ namespace ix
|
||||
WebSocketMessageType webSocketMessageType;
|
||||
switch (messageKind)
|
||||
{
|
||||
case WebSocketTransport::MSG:
|
||||
default:
|
||||
case WebSocketTransport::MessageKind::MSG:
|
||||
{
|
||||
webSocketMessageType = WebSocket_MessageType_Message;
|
||||
webSocketMessageType = WebSocketMessageType::Message;
|
||||
} break;
|
||||
|
||||
case WebSocketTransport::PING:
|
||||
case WebSocketTransport::MessageKind::PING:
|
||||
{
|
||||
webSocketMessageType = WebSocket_MessageType_Ping;
|
||||
webSocketMessageType = WebSocketMessageType::Ping;
|
||||
} break;
|
||||
|
||||
case WebSocketTransport::PONG:
|
||||
case WebSocketTransport::MessageKind::PONG:
|
||||
{
|
||||
webSocketMessageType = WebSocket_MessageType_Pong;
|
||||
webSocketMessageType = WebSocketMessageType::Pong;
|
||||
} break;
|
||||
|
||||
case WebSocketTransport::FRAGMENT:
|
||||
case WebSocketTransport::MessageKind::FRAGMENT:
|
||||
{
|
||||
webSocketMessageType = WebSocket_MessageType_Fragment;
|
||||
webSocketMessageType = WebSocketMessageType::Fragment;
|
||||
} break;
|
||||
}
|
||||
|
||||
@ -316,12 +337,6 @@ namespace ix
|
||||
|
||||
WebSocket::invokeTrafficTrackerCallback(msg.size(), true);
|
||||
});
|
||||
|
||||
// 4. In blocking mode, getting out of this function is triggered by
|
||||
// an explicit disconnection from the callback, or by the remote end
|
||||
// closing the connection, ie isConnected() == false.
|
||||
// closing the connection, ie isConnectedOrClosing() == false.
|
||||
if (!_backgroundThreadRunning && !isConnected() && !_automaticReconnection) return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -348,10 +363,10 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketSendInfo WebSocket::send(const std::string& text,
|
||||
WebSocketSendInfo WebSocket::send(const std::string& data,
|
||||
const OnProgressCallback& onProgressCallback)
|
||||
{
|
||||
return sendMessage(text, SendMessageKind::Binary, onProgressCallback);
|
||||
return sendMessage(data, SendMessageKind::Binary, onProgressCallback);
|
||||
}
|
||||
|
||||
WebSocketSendInfo WebSocket::sendText(const std::string& text,
|
||||
@ -414,11 +429,11 @@ namespace ix
|
||||
{
|
||||
switch (_ws.getReadyState())
|
||||
{
|
||||
case ix::WebSocketTransport::OPEN: return WebSocket_ReadyState_Open;
|
||||
case ix::WebSocketTransport::CONNECTING: return WebSocket_ReadyState_Connecting;
|
||||
case ix::WebSocketTransport::CLOSING: return WebSocket_ReadyState_Closing;
|
||||
case ix::WebSocketTransport::CLOSED: return WebSocket_ReadyState_Closed;
|
||||
default: return WebSocket_ReadyState_Closed;
|
||||
case ix::WebSocketTransport::ReadyState::OPEN : return ReadyState::Open;
|
||||
case ix::WebSocketTransport::ReadyState::CONNECTING: return ReadyState::Connecting;
|
||||
case ix::WebSocketTransport::ReadyState::CLOSING : return ReadyState::Closing;
|
||||
case ix::WebSocketTransport::ReadyState::CLOSED : return ReadyState::Closed;
|
||||
default: return ReadyState::Closed;
|
||||
}
|
||||
}
|
||||
|
||||
@ -426,11 +441,11 @@ namespace ix
|
||||
{
|
||||
switch (readyState)
|
||||
{
|
||||
case WebSocket_ReadyState_Open: return "OPEN";
|
||||
case WebSocket_ReadyState_Connecting: return "CONNECTING";
|
||||
case WebSocket_ReadyState_Closing: return "CLOSING";
|
||||
case WebSocket_ReadyState_Closed: return "CLOSED";
|
||||
default: return "CLOSED";
|
||||
case ReadyState::Open : return "OPEN";
|
||||
case ReadyState::Connecting: return "CONNECTING";
|
||||
case ReadyState::Closing : return "CLOSING";
|
||||
case ReadyState::Closed : return "CLOSED";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,23 +24,23 @@
|
||||
namespace ix
|
||||
{
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants
|
||||
enum ReadyState
|
||||
enum class ReadyState
|
||||
{
|
||||
WebSocket_ReadyState_Connecting = 0,
|
||||
WebSocket_ReadyState_Open = 1,
|
||||
WebSocket_ReadyState_Closing = 2,
|
||||
WebSocket_ReadyState_Closed = 3
|
||||
Connecting = 0,
|
||||
Open = 1,
|
||||
Closing = 2,
|
||||
Closed = 3
|
||||
};
|
||||
|
||||
enum WebSocketMessageType
|
||||
enum class WebSocketMessageType
|
||||
{
|
||||
WebSocket_MessageType_Message = 0,
|
||||
WebSocket_MessageType_Open = 1,
|
||||
WebSocket_MessageType_Close = 2,
|
||||
WebSocket_MessageType_Error = 3,
|
||||
WebSocket_MessageType_Ping = 4,
|
||||
WebSocket_MessageType_Pong = 5,
|
||||
WebSocket_MessageType_Fragment = 6
|
||||
Message = 0,
|
||||
Open = 1,
|
||||
Close = 2,
|
||||
Error = 3,
|
||||
Ping = 4,
|
||||
Pong = 5,
|
||||
Fragment = 6
|
||||
};
|
||||
|
||||
struct WebSocketOpenInfo
|
||||
@ -91,7 +91,6 @@ namespace ix
|
||||
|
||||
void setUrl(const std::string& url);
|
||||
void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
|
||||
void setHandshakeTimeout(int handshakeTimeoutSecs);
|
||||
void setHeartBeatPeriod(int heartBeatPeriodSecs);
|
||||
void setPingInterval(int pingIntervalSecs); // alias of setHeartBeatPeriod
|
||||
void setPingTimeout(int pingTimeoutSecs);
|
||||
@ -100,24 +99,32 @@ namespace ix
|
||||
|
||||
// Run asynchronously, by calling start and stop.
|
||||
void start();
|
||||
// stop is synchronous
|
||||
void stop();
|
||||
|
||||
// Run in blocking mode, by connecting first manually, and then calling run.
|
||||
WebSocketInitResult connect(int timeoutSecs);
|
||||
void run();
|
||||
|
||||
WebSocketSendInfo send(const std::string& text,
|
||||
// send binary data
|
||||
WebSocketSendInfo send(const std::string& data,
|
||||
const OnProgressCallback& onProgressCallback = nullptr);
|
||||
WebSocketSendInfo sendText(const std::string& text,
|
||||
const OnProgressCallback& onProgressCallback = nullptr);
|
||||
WebSocketSendInfo ping(const std::string& text);
|
||||
void close();
|
||||
|
||||
// A close frame can provide a code and a reason
|
||||
// FIXME: use constants
|
||||
void close(uint16_t code = 1000,
|
||||
const std::string& reason = "Normal closure");
|
||||
|
||||
void setOnMessageCallback(const OnMessageCallback& callback);
|
||||
static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback);
|
||||
static void resetTrafficTrackerCallback();
|
||||
|
||||
ReadyState getReadyState() const;
|
||||
static std::string readyStateToString(ReadyState readyState);
|
||||
|
||||
const std::string& getUrl() const;
|
||||
const WebSocketPerMessageDeflateOptions& getPerMessageDeflateOptions() const;
|
||||
int getHeartBeatPeriod() const;
|
||||
@ -136,12 +143,10 @@ namespace ix
|
||||
|
||||
bool isConnected() const;
|
||||
bool isClosing() const;
|
||||
void reconnectPerpetuallyIfDisconnected();
|
||||
std::string readyStateToString(ReadyState readyState);
|
||||
void checkConnection(bool firstConnectionAttempt);
|
||||
static void invokeTrafficTrackerCallback(size_t size, bool incoming);
|
||||
|
||||
// Server
|
||||
void setSocketFileDescriptor(int fd);
|
||||
WebSocketInitResult connectToSocket(int fd, int timeoutSecs);
|
||||
|
||||
WebSocketTransport _ws;
|
||||
@ -154,7 +159,6 @@ namespace ix
|
||||
static OnTrafficTrackerCallback _onTrafficTrackerCallback;
|
||||
|
||||
std::atomic<bool> _stop;
|
||||
std::atomic<bool> _backgroundThreadRunning;
|
||||
std::atomic<bool> _automaticReconnection;
|
||||
std::thread _thread;
|
||||
std::mutex _writeMutex;
|
||||
|
@ -12,10 +12,10 @@ namespace ix
|
||||
{
|
||||
struct WebSocketErrorInfo
|
||||
{
|
||||
uint64_t retries;
|
||||
double wait_time;
|
||||
int http_status;
|
||||
uint32_t retries = 0;
|
||||
double wait_time = 0;
|
||||
int http_status = 0;
|
||||
std::string reason;
|
||||
bool decompressionError;
|
||||
bool decompressionError = false;
|
||||
};
|
||||
}
|
||||
|
@ -10,9 +10,7 @@
|
||||
|
||||
#include "libwshandshake.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include "IXWebSocketPerMessageDeflateCodec.h"
|
||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
#include <string.h>
|
||||
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
|
@ -23,7 +23,6 @@ namespace ix
|
||||
WebSocketPerMessageDeflateOptions(std::string extension);
|
||||
|
||||
std::string generateHeader();
|
||||
std::string parseHeader();
|
||||
bool enabled() const;
|
||||
bool getClientNoContextTakeover() const;
|
||||
bool getServerNoContextTakeover() const;
|
||||
|
@ -6,9 +6,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct WebSocketSendInfo
|
||||
|
@ -23,7 +23,7 @@ namespace ix
|
||||
using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>,
|
||||
std::shared_ptr<ConnectionState>)>;
|
||||
|
||||
class WebSocketServer : public SocketServer {
|
||||
class WebSocketServer final : public SocketServer {
|
||||
public:
|
||||
WebSocketServer(int port = SocketServer::kDefaultPort,
|
||||
const std::string& host = SocketServer::kDefaultHost,
|
||||
|
@ -45,7 +45,6 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdarg>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
@ -72,28 +71,34 @@ namespace ix
|
||||
const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
|
||||
const int WebSocketTransport::kDefaultPingTimeoutSecs(-1);
|
||||
const bool WebSocketTransport::kDefaultEnablePong(true);
|
||||
const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(200);
|
||||
constexpr size_t WebSocketTransport::kChunkSize;
|
||||
|
||||
const uint16_t WebSocketTransport::kInternalErrorCode(1011);
|
||||
const uint16_t WebSocketTransport::kAbnormalCloseCode(1006);
|
||||
const uint16_t WebSocketTransport::kProtocolErrorCode(1002);
|
||||
const uint16_t WebSocketTransport::kNoStatusCodeErrorCode(1005);
|
||||
const std::string WebSocketTransport::kInternalErrorMessage("Internal error");
|
||||
const std::string WebSocketTransport::kAbnormalCloseMessage("Abnormal closure");
|
||||
const std::string WebSocketTransport::kPingTimeoutMessage("Ping timeout");
|
||||
const std::string WebSocketTransport::kProtocolErrorMessage("Protocol error");
|
||||
const std::string WebSocketTransport::kNoStatusCodeErrorMessage("No status code");
|
||||
|
||||
WebSocketTransport::WebSocketTransport() :
|
||||
_useMask(true),
|
||||
_readyState(CLOSED),
|
||||
_readyState(ReadyState::CLOSED),
|
||||
_closeCode(kInternalErrorCode),
|
||||
_closeReason(kInternalErrorMessage),
|
||||
_closeWireSize(0),
|
||||
_closeRemote(false),
|
||||
_enablePerMessageDeflate(false),
|
||||
_requestInitCancellation(false),
|
||||
_closingTimePoint(std::chrono::steady_clock::now()),
|
||||
_enablePong(kDefaultEnablePong),
|
||||
_pingIntervalSecs(kDefaultPingIntervalSecs),
|
||||
_pingTimeoutSecs(kDefaultPingTimeoutSecs),
|
||||
_pingIntervalOrTimeoutGCDSecs(-1),
|
||||
_nextGCDTimePoint(std::chrono::steady_clock::now()),
|
||||
_lastSendPingTimePoint(std::chrono::steady_clock::now()),
|
||||
_lastReceivePongTimePoint(std::chrono::steady_clock::now())
|
||||
{
|
||||
@ -137,9 +142,8 @@ namespace ix
|
||||
{
|
||||
std::string protocol, host, path, query;
|
||||
int port;
|
||||
bool websocket = true;
|
||||
|
||||
if (!UrlParser::parse(url, protocol, host, path, query, port, websocket))
|
||||
if (!UrlParser::parse(url, protocol, host, path, query, port))
|
||||
{
|
||||
return WebSocketInitResult(false, 0,
|
||||
std::string("Could not parse URL ") + url);
|
||||
@ -164,7 +168,7 @@ namespace ix
|
||||
timeoutSecs);
|
||||
if (result.success)
|
||||
{
|
||||
setReadyState(OPEN);
|
||||
setReadyState(ReadyState::OPEN);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -192,22 +196,22 @@ namespace ix
|
||||
auto result = webSocketHandshake.serverHandshake(fd, timeoutSecs);
|
||||
if (result.success)
|
||||
{
|
||||
setReadyState(OPEN);
|
||||
setReadyState(ReadyState::OPEN);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
WebSocketTransport::ReadyStateValues WebSocketTransport::getReadyState() const
|
||||
WebSocketTransport::ReadyState WebSocketTransport::getReadyState() const
|
||||
{
|
||||
return _readyState;
|
||||
}
|
||||
|
||||
void WebSocketTransport::setReadyState(ReadyStateValues readyStateValue)
|
||||
void WebSocketTransport::setReadyState(ReadyState readyState)
|
||||
{
|
||||
// No state change, return
|
||||
if (_readyState == readyStateValue) return;
|
||||
if (_readyState == readyState) return;
|
||||
|
||||
if (readyStateValue == CLOSED)
|
||||
if (readyState == ReadyState::CLOSED)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||
_onCloseCallback(_closeCode, _closeReason, _closeWireSize, _closeRemote);
|
||||
@ -216,8 +220,12 @@ namespace ix
|
||||
_closeWireSize = 0;
|
||||
_closeRemote = false;
|
||||
}
|
||||
else if (readyState == ReadyState::OPEN)
|
||||
{
|
||||
initTimePointsAndGCDAfterConnect();
|
||||
}
|
||||
|
||||
_readyState = readyStateValue;
|
||||
_readyState = readyState;
|
||||
}
|
||||
|
||||
void WebSocketTransport::setOnCloseCallback(const OnCloseCallback& onCloseCallback)
|
||||
@ -225,6 +233,23 @@ namespace ix
|
||||
_onCloseCallback = onCloseCallback;
|
||||
}
|
||||
|
||||
void WebSocketTransport::initTimePointsAndGCDAfterConnect()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_lastSendPingTimePointMutex);
|
||||
_lastSendPingTimePoint = std::chrono::steady_clock::now();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_lastReceivePongTimePointMutex);
|
||||
_lastReceivePongTimePoint = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
if (_pingIntervalOrTimeoutGCDSecs > 0)
|
||||
{
|
||||
_nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
|
||||
}
|
||||
}
|
||||
|
||||
// Only consider send PING time points for that computation.
|
||||
bool WebSocketTransport::pingIntervalExceeded()
|
||||
{
|
||||
@ -246,11 +271,16 @@ namespace ix
|
||||
return now - _lastReceivePongTimePoint > std::chrono::seconds(_pingTimeoutSecs);
|
||||
}
|
||||
|
||||
void WebSocketTransport::poll()
|
||||
bool WebSocketTransport::closingDelayExceeded()
|
||||
{
|
||||
PollResultType pollResult = _socket->poll(_pingIntervalOrTimeoutGCDSecs);
|
||||
std::lock_guard<std::mutex> lock(_closingTimePointMutex);
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
return now - _closingTimePoint > std::chrono::milliseconds(kClosingMaximumWaitingDelayInMs);
|
||||
}
|
||||
|
||||
if (_readyState == OPEN)
|
||||
WebSocketTransport::PollResult WebSocketTransport::poll()
|
||||
{
|
||||
if (_readyState == ReadyState::OPEN)
|
||||
{
|
||||
// if (1) ping timeout is enabled and (2) duration since last received
|
||||
// ping response (PONG) exceeds the maximum delay, then close the connection
|
||||
@ -267,6 +297,34 @@ namespace ix
|
||||
sendPing(ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
// No timeout if state is not OPEN, otherwise computed
|
||||
// pingIntervalOrTimeoutGCD (equals to -1 if no ping and no ping timeout are set)
|
||||
int lastingTimeoutDelayInMs = (_readyState != ReadyState::OPEN) ? 0 : _pingIntervalOrTimeoutGCDSecs;
|
||||
|
||||
if (_pingIntervalOrTimeoutGCDSecs > 0)
|
||||
{
|
||||
// compute lasting delay to wait for next ping / timeout, if at least one set
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (now >= _nextGCDTimePoint)
|
||||
{
|
||||
_nextGCDTimePoint = now + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
|
||||
|
||||
lastingTimeoutDelayInMs = _pingIntervalOrTimeoutGCDSecs * 1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastingTimeoutDelayInMs = (int)std::chrono::duration_cast<std::chrono::milliseconds>(_nextGCDTimePoint - now).count();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (lastingTimeoutDelayInMs <= 0) lastingTimeoutDelayInMs = 20;
|
||||
#endif
|
||||
|
||||
// poll the socket
|
||||
PollResultType pollResult = _socket->poll(lastingTimeoutDelayInMs);
|
||||
|
||||
// Make sure we send all the buffered data
|
||||
// there can be a lot of it for large messages.
|
||||
@ -281,7 +339,7 @@ namespace ix
|
||||
if (result == PollResultType::Error)
|
||||
{
|
||||
_socket->close();
|
||||
setReadyState(CLOSED);
|
||||
setReadyState(ReadyState::CLOSED);
|
||||
break;
|
||||
}
|
||||
else if (result == PollResultType::ReadyForWrite)
|
||||
@ -296,24 +354,18 @@ namespace ix
|
||||
{
|
||||
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
|
||||
|
||||
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
|
||||
_socket->getErrno() == EAGAIN))
|
||||
if (ret < 0 && Socket::isWaitNeeded())
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (ret <= 0)
|
||||
{
|
||||
_rxbuf.clear();
|
||||
// if there are received data pending to be processed, then delay the abnormal closure
|
||||
// to after dispatch (other close code/reason could be read from the buffer)
|
||||
|
||||
_socket->close();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||
_closeCode = kAbnormalCloseCode;
|
||||
_closeReason = kAbnormalCloseMessage;
|
||||
_closeWireSize = 0;
|
||||
_closeRemote = true;
|
||||
}
|
||||
setReadyState(CLOSED);
|
||||
break;
|
||||
|
||||
return PollResult::AbnormalClose;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -332,12 +384,15 @@ namespace ix
|
||||
_socket->close();
|
||||
}
|
||||
|
||||
// Avoid a race condition where we get stuck in select
|
||||
// while closing.
|
||||
if (_readyState == CLOSING)
|
||||
if (_readyState == ReadyState::CLOSING && closingDelayExceeded())
|
||||
{
|
||||
_rxbuf.clear();
|
||||
// close code and reason were set when calling close()
|
||||
_socket->close();
|
||||
setReadyState(ReadyState::CLOSED);
|
||||
}
|
||||
|
||||
return PollResult::Succeeded;
|
||||
}
|
||||
|
||||
bool WebSocketTransport::isSendBufferEmpty() const
|
||||
@ -399,12 +454,13 @@ namespace ix
|
||||
// | Payload Data continued ... |
|
||||
// +---------------------------------------------------------------+
|
||||
//
|
||||
void WebSocketTransport::dispatch(const OnMessageCallback& onMessageCallback)
|
||||
void WebSocketTransport::dispatch(WebSocketTransport::PollResult pollResult,
|
||||
const OnMessageCallback& onMessageCallback)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
wsheader_type ws;
|
||||
if (_rxbuf.size() < 2) return; /* Need at least 2 */
|
||||
if (_rxbuf.size() < 2) break; /* Need at least 2 */
|
||||
const uint8_t * data = (uint8_t *) &_rxbuf[0]; // peek, but don't consume
|
||||
ws.fin = (data[0] & 0x80) == 0x80;
|
||||
ws.rsv1 = (data[0] & 0x40) == 0x40;
|
||||
@ -412,7 +468,7 @@ namespace ix
|
||||
ws.mask = (data[1] & 0x80) == 0x80;
|
||||
ws.N0 = (data[1] & 0x7f);
|
||||
ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);
|
||||
if (_rxbuf.size() < ws.header_size) return; /* Need: ws.header_size - _rxbuf.size() */
|
||||
if (_rxbuf.size() < ws.header_size) break; /* Need: ws.header_size - _rxbuf.size() */
|
||||
|
||||
//
|
||||
// Calculate payload length:
|
||||
@ -485,7 +541,7 @@ namespace ix
|
||||
//
|
||||
if (ws.fin && _chunks.empty())
|
||||
{
|
||||
emitMessage(MSG,
|
||||
emitMessage(MessageKind::MSG,
|
||||
std::string(_rxbuf.begin()+ws.header_size,
|
||||
_rxbuf.begin()+ws.header_size+(size_t) ws.N),
|
||||
ws,
|
||||
@ -505,12 +561,12 @@ namespace ix
|
||||
_rxbuf.begin()+ws.header_size+(size_t)ws.N));
|
||||
if (ws.fin)
|
||||
{
|
||||
emitMessage(MSG, getMergedChunks(), ws, onMessageCallback);
|
||||
emitMessage(MessageKind::MSG, getMergedChunks(), ws, onMessageCallback);
|
||||
_chunks.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
emitMessage(FRAGMENT, std::string(), ws, onMessageCallback);
|
||||
emitMessage(MessageKind::FRAGMENT, std::string(), ws, onMessageCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -528,7 +584,7 @@ namespace ix
|
||||
sendData(wsheader_type::PONG, pingData, compress);
|
||||
}
|
||||
|
||||
emitMessage(PING, pingData, ws, onMessageCallback);
|
||||
emitMessage(MessageKind::PING, pingData, ws, onMessageCallback);
|
||||
}
|
||||
else if (ws.opcode == wsheader_type::PONG)
|
||||
{
|
||||
@ -539,24 +595,62 @@ namespace ix
|
||||
std::lock_guard<std::mutex> lck(_lastReceivePongTimePointMutex);
|
||||
_lastReceivePongTimePoint = std::chrono::steady_clock::now();
|
||||
|
||||
emitMessage(PONG, pongData, ws, onMessageCallback);
|
||||
emitMessage(MessageKind::PONG, pongData, ws, onMessageCallback);
|
||||
}
|
||||
else if (ws.opcode == wsheader_type::CLOSE)
|
||||
{
|
||||
std::string reason;
|
||||
uint16_t code = 0;
|
||||
|
||||
unmaskReceiveBuffer(ws);
|
||||
|
||||
// Extract the close code first, available as the first 2 bytes
|
||||
uint16_t code = 0;
|
||||
code |= ((uint64_t) _rxbuf[ws.header_size]) << 8;
|
||||
code |= ((uint64_t) _rxbuf[ws.header_size+1]) << 0;
|
||||
if (ws.N >= 2)
|
||||
{
|
||||
// Extract the close code first, available as the first 2 bytes
|
||||
code |= ((uint64_t) _rxbuf[ws.header_size]) << 8;
|
||||
code |= ((uint64_t) _rxbuf[ws.header_size+1]) << 0;
|
||||
|
||||
// Get the reason.
|
||||
std::string reason(_rxbuf.begin()+ws.header_size + 2,
|
||||
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
|
||||
|
||||
bool remote = true;
|
||||
// Get the reason.
|
||||
if (ws.N > 2)
|
||||
{
|
||||
reason.assign(_rxbuf.begin()+ws.header_size + 2,
|
||||
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no close code received
|
||||
code = kNoStatusCodeErrorCode;
|
||||
reason = kNoStatusCodeErrorMessage;
|
||||
}
|
||||
|
||||
close(code, reason, _rxbuf.size(), remote);
|
||||
// We receive a CLOSE frame from remote and are NOT the ones who triggered the close
|
||||
if (_readyState != ReadyState::CLOSING)
|
||||
{
|
||||
// send back the CLOSE frame
|
||||
sendCloseFrame(code, reason);
|
||||
|
||||
_socket->wakeUpFromPoll(Socket::kCloseRequest);
|
||||
|
||||
bool remote = true;
|
||||
closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we got the CLOSE frame answer from our close, so we can close the connection if
|
||||
// the code/reason are the same
|
||||
bool identicalReason;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||
identicalReason = _closeCode == code && _closeReason == reason;
|
||||
}
|
||||
|
||||
if (identicalReason)
|
||||
{
|
||||
bool remote = false;
|
||||
closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -569,6 +663,25 @@ namespace ix
|
||||
_rxbuf.erase(_rxbuf.begin(),
|
||||
_rxbuf.begin() + ws.header_size + (size_t) ws.N);
|
||||
}
|
||||
|
||||
// if an abnormal closure was raised in poll, and nothing else triggered a CLOSED state in
|
||||
// the received and processed data then close the connection
|
||||
if (pollResult == PollResult::AbnormalClose)
|
||||
{
|
||||
_rxbuf.clear();
|
||||
|
||||
// if we previously closed the connection (CLOSING state), then set state to CLOSED (code/reason were set before)
|
||||
if (_readyState == ReadyState::CLOSING)
|
||||
{
|
||||
_socket->close();
|
||||
setReadyState(ReadyState::CLOSED);
|
||||
}
|
||||
// if we weren't closing, then close using abnormal close code and message
|
||||
else if (_readyState != ReadyState::CLOSED)
|
||||
{
|
||||
closeSocketAndSwitchToClosedState(kAbnormalCloseCode, kAbnormalCloseMessage, 0, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string WebSocketTransport::getMergedChunks() const
|
||||
@ -599,7 +712,7 @@ namespace ix
|
||||
size_t wireSize = message.size();
|
||||
|
||||
// When the RSV1 bit is 1 it means the message is compressed
|
||||
if (_enablePerMessageDeflate && ws.rsv1 && messageKind != FRAGMENT)
|
||||
if (_enablePerMessageDeflate && ws.rsv1 && messageKind != MessageKind::FRAGMENT)
|
||||
{
|
||||
std::string decompressedMessage;
|
||||
bool success = _perMessageDeflate.decompress(message, decompressedMessage);
|
||||
@ -626,7 +739,7 @@ namespace ix
|
||||
bool compress,
|
||||
const OnProgressCallback& onProgressCallback)
|
||||
{
|
||||
if (_readyState == CLOSING || _readyState == CLOSED)
|
||||
if (_readyState != ReadyState::OPEN)
|
||||
{
|
||||
return WebSocketSendInfo();
|
||||
}
|
||||
@ -844,8 +957,7 @@ namespace ix
|
||||
{
|
||||
ssize_t ret = _socket->send((char*)&_txbuf[0], _txbuf.size());
|
||||
|
||||
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
|
||||
_socket->getErrno() == EAGAIN))
|
||||
if (ret < 0 && Socket::isWaitNeeded())
|
||||
{
|
||||
break;
|
||||
}
|
||||
@ -853,7 +965,7 @@ namespace ix
|
||||
{
|
||||
_socket->close();
|
||||
|
||||
setReadyState(CLOSED);
|
||||
setReadyState(ReadyState::CLOSED);
|
||||
break;
|
||||
}
|
||||
else
|
||||
@ -863,29 +975,32 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketTransport::close(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
|
||||
void WebSocketTransport::sendCloseFrame(uint16_t code, const std::string& reason)
|
||||
{
|
||||
_requestInitCancellation = true;
|
||||
|
||||
if (_readyState == CLOSING || _readyState == CLOSED) return;
|
||||
|
||||
// See list of close events here:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
||||
|
||||
int codeLength = 2;
|
||||
std::string closure{(char)(code >> 8), (char)(code & 0xff)};
|
||||
closure.resize(codeLength + reason.size());
|
||||
|
||||
// copy reason after code
|
||||
closure.replace(codeLength, reason.size(), reason);
|
||||
|
||||
bool compress = false;
|
||||
sendData(wsheader_type::CLOSE, closure, compress);
|
||||
setReadyState(CLOSING);
|
||||
|
||||
_socket->wakeUpFromPoll(Socket::kCloseRequest);
|
||||
// if a status is set/was read
|
||||
if (code != kNoStatusCodeErrorCode)
|
||||
{
|
||||
// See list of close events here:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
||||
std::string closure{(char)(code >> 8), (char)(code & 0xff)};
|
||||
|
||||
// copy reason after code
|
||||
closure.append(reason);
|
||||
|
||||
sendData(wsheader_type::CLOSE, closure, compress);
|
||||
}
|
||||
else
|
||||
{
|
||||
// no close code/reason set
|
||||
sendData(wsheader_type::CLOSE, "", compress);
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketTransport::closeSocketAndSwitchToClosedState(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
|
||||
{
|
||||
_socket->close();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||
_closeCode = code;
|
||||
@ -893,8 +1008,31 @@ namespace ix
|
||||
_closeWireSize = closeWireSize;
|
||||
_closeRemote = remote;
|
||||
}
|
||||
setReadyState(ReadyState::CLOSED);
|
||||
}
|
||||
|
||||
setReadyState(CLOSED);
|
||||
void WebSocketTransport::close(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
|
||||
{
|
||||
_requestInitCancellation = true;
|
||||
|
||||
if (_readyState == ReadyState::CLOSING || _readyState == ReadyState::CLOSED) return;
|
||||
|
||||
sendCloseFrame(code, reason);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||
_closeCode = code;
|
||||
_closeReason = reason;
|
||||
_closeWireSize = closeWireSize;
|
||||
_closeRemote = remote;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_closingTimePointMutex);
|
||||
_closingTimePoint = std::chrono::steady_clock::now();
|
||||
}
|
||||
setReadyState(ReadyState::CLOSING);
|
||||
|
||||
// wake up the poll, but do not close yet
|
||||
_socket->wakeUpFromPoll(Socket::kSendRequest);
|
||||
}
|
||||
|
||||
size_t WebSocketTransport::bufferedAmount() const
|
||||
|
@ -40,7 +40,7 @@ namespace ix
|
||||
class WebSocketTransport
|
||||
{
|
||||
public:
|
||||
enum ReadyStateValues
|
||||
enum class ReadyState
|
||||
{
|
||||
CLOSING,
|
||||
CLOSED,
|
||||
@ -48,7 +48,7 @@ namespace ix
|
||||
OPEN
|
||||
};
|
||||
|
||||
enum MessageKind
|
||||
enum class MessageKind
|
||||
{
|
||||
MSG,
|
||||
PING,
|
||||
@ -56,6 +56,12 @@ namespace ix
|
||||
FRAGMENT
|
||||
};
|
||||
|
||||
enum class PollResult
|
||||
{
|
||||
Succeeded,
|
||||
AbnormalClose
|
||||
};
|
||||
|
||||
using OnMessageCallback = std::function<void(const std::string&,
|
||||
size_t,
|
||||
bool,
|
||||
@ -78,7 +84,7 @@ namespace ix
|
||||
WebSocketInitResult connectToSocket(int fd, // Server
|
||||
int timeoutSecs);
|
||||
|
||||
void poll();
|
||||
PollResult poll();
|
||||
WebSocketSendInfo sendBinary(const std::string& message,
|
||||
const OnProgressCallback& onProgressCallback);
|
||||
WebSocketSendInfo sendText(const std::string& message,
|
||||
@ -90,10 +96,11 @@ namespace ix
|
||||
size_t closeWireSize = 0,
|
||||
bool remote = false);
|
||||
|
||||
ReadyStateValues getReadyState() const;
|
||||
void setReadyState(ReadyStateValues readyStateValue);
|
||||
ReadyState getReadyState() const;
|
||||
void setReadyState(ReadyState readyState);
|
||||
void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
|
||||
void dispatch(const OnMessageCallback& onMessageCallback);
|
||||
void dispatch(PollResult pollResult,
|
||||
const OnMessageCallback& onMessageCallback);
|
||||
size_t bufferedAmount() const;
|
||||
|
||||
private:
|
||||
@ -106,11 +113,11 @@ namespace ix
|
||||
bool mask;
|
||||
enum opcode_type {
|
||||
CONTINUATION = 0x0,
|
||||
TEXT_FRAME = 0x1,
|
||||
TEXT_FRAME = 0x1,
|
||||
BINARY_FRAME = 0x2,
|
||||
CLOSE = 8,
|
||||
PING = 9,
|
||||
PONG = 0xa,
|
||||
CLOSE = 8,
|
||||
PING = 9,
|
||||
PONG = 0xa,
|
||||
} opcode;
|
||||
int N0;
|
||||
uint64_t N;
|
||||
@ -146,7 +153,7 @@ namespace ix
|
||||
std::shared_ptr<Socket> _socket;
|
||||
|
||||
// Hold the state of the connection (OPEN, CLOSED, etc...)
|
||||
std::atomic<ReadyStateValues> _readyState;
|
||||
std::atomic<ReadyState> _readyState;
|
||||
|
||||
OnCloseCallback _onCloseCallback;
|
||||
uint16_t _closeCode;
|
||||
@ -162,18 +169,24 @@ namespace ix
|
||||
|
||||
// Used to cancel dns lookup + socket connect + http upgrade
|
||||
std::atomic<bool> _requestInitCancellation;
|
||||
|
||||
mutable std::mutex _closingTimePointMutex;
|
||||
std::chrono::time_point<std::chrono::steady_clock>_closingTimePoint;
|
||||
static const int kClosingMaximumWaitingDelayInMs;
|
||||
|
||||
// Constants for dealing with closing conneections
|
||||
static const uint16_t kInternalErrorCode;
|
||||
static const uint16_t kAbnormalCloseCode;
|
||||
static const uint16_t kProtocolErrorCode;
|
||||
static const uint16_t kNoStatusCodeErrorCode;
|
||||
static const std::string kInternalErrorMessage;
|
||||
static const std::string kAbnormalCloseMessage;
|
||||
static const std::string kPingTimeoutMessage;
|
||||
static const std::string kProtocolErrorMessage;
|
||||
static const std::string kNoStatusCodeErrorMessage;
|
||||
|
||||
// enable auto response to ping
|
||||
bool _enablePong;
|
||||
std::atomic<bool> _enablePong;
|
||||
static const bool kDefaultEnablePong;
|
||||
|
||||
// Optional ping and pong timeout
|
||||
@ -187,6 +200,9 @@ namespace ix
|
||||
static const int kDefaultPingTimeoutSecs;
|
||||
static const std::string kPingMessage;
|
||||
|
||||
// Record time step for ping/ ping timeout to ensure we wait for the right left duration
|
||||
std::chrono::time_point<std::chrono::steady_clock> _nextGCDTimePoint;
|
||||
|
||||
// We record when ping are being sent so that we can know when to send the next one
|
||||
// We also record when pong are being sent as a reply to pings, to close the connections
|
||||
// if no pong were received sufficiently fast.
|
||||
@ -201,6 +217,18 @@ namespace ix
|
||||
// No PONG data was received through the socket for longer than ping timeout delay
|
||||
bool pingTimeoutExceeded();
|
||||
|
||||
// after calling close(), if no CLOSE frame answer is received back from the remote, we should close the connexion
|
||||
bool closingDelayExceeded();
|
||||
|
||||
void initTimePointsAndGCDAfterConnect();
|
||||
|
||||
void sendCloseFrame(uint16_t code, const std::string& reason);
|
||||
|
||||
void closeSocketAndSwitchToClosedState(uint16_t code,
|
||||
const std::string& reason,
|
||||
size_t closeWireSize,
|
||||
bool remote);
|
||||
|
||||
void sendOnSocket();
|
||||
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
|
||||
const std::string& message,
|
||||
|
263
ixwebsocket/LUrlParser.cpp
Normal file
263
ixwebsocket/LUrlParser.cpp
Normal file
@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
||||
* https://github.com/corporateshark/LUrlParser
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "LUrlParser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <stdlib.h>
|
||||
|
||||
// check if the scheme name is valid
|
||||
static bool IsSchemeValid( const std::string& SchemeName )
|
||||
{
|
||||
for ( auto c : SchemeName )
|
||||
{
|
||||
if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LUrlParser::clParseURL::GetPort( int* OutPort ) const
|
||||
{
|
||||
if ( !IsValid() ) { return false; }
|
||||
|
||||
int Port = atoi( m_Port.c_str() );
|
||||
|
||||
if ( Port <= 0 || Port > 65535 ) { return false; }
|
||||
|
||||
if ( OutPort ) { *OutPort = Port; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// based on RFC 1738 and RFC 3986
|
||||
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL )
|
||||
{
|
||||
LUrlParser::clParseURL Result;
|
||||
|
||||
const char* CurrentString = URL.c_str();
|
||||
|
||||
/*
|
||||
* <scheme>:<scheme-specific-part>
|
||||
* <scheme> := [a-z\+\-\.]+
|
||||
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names
|
||||
*/
|
||||
|
||||
// try to read scheme
|
||||
{
|
||||
const char* LocalString = strchr( CurrentString, ':' );
|
||||
|
||||
if ( !LocalString )
|
||||
{
|
||||
return clParseURL( LUrlParserError_NoUrlCharacter );
|
||||
}
|
||||
|
||||
// save the scheme name
|
||||
Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
if ( !IsSchemeValid( Result.m_Scheme ) )
|
||||
{
|
||||
return clParseURL( LUrlParserError_InvalidSchemeName );
|
||||
}
|
||||
|
||||
// scheme should be lowercase
|
||||
std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower );
|
||||
|
||||
// skip ':'
|
||||
CurrentString = LocalString+1;
|
||||
}
|
||||
|
||||
/*
|
||||
* //<user>:<password>@<host>:<port>/<url-path>
|
||||
* any ":", "@" and "/" must be normalized
|
||||
*/
|
||||
|
||||
// skip "//"
|
||||
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
|
||||
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
|
||||
|
||||
// check if the user name and password are specified
|
||||
bool bHasUserName = false;
|
||||
|
||||
const char* LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString )
|
||||
{
|
||||
if ( *LocalString == '@' )
|
||||
{
|
||||
// user name and password are specified
|
||||
bHasUserName = true;
|
||||
break;
|
||||
}
|
||||
else if ( *LocalString == '/' )
|
||||
{
|
||||
// end of <host>:<port> specification
|
||||
bHasUserName = false;
|
||||
break;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
}
|
||||
|
||||
// user name and password
|
||||
LocalString = CurrentString;
|
||||
|
||||
if ( bHasUserName )
|
||||
{
|
||||
// read user name
|
||||
while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++;
|
||||
|
||||
Result.m_UserName = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
// proceed with the current pointer
|
||||
CurrentString = LocalString;
|
||||
|
||||
if ( *CurrentString == ':' )
|
||||
{
|
||||
// skip ':'
|
||||
CurrentString++;
|
||||
|
||||
// read password
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString && *LocalString != '@' ) LocalString++;
|
||||
|
||||
Result.m_Password = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// skip '@'
|
||||
if ( *CurrentString != '@' )
|
||||
{
|
||||
return clParseURL( LUrlParserError_NoAtSign );
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
}
|
||||
|
||||
bool bHasBracket = ( *CurrentString == '[' );
|
||||
|
||||
// go ahead, read the host name
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString )
|
||||
{
|
||||
if ( bHasBracket && *LocalString == ']' )
|
||||
{
|
||||
// end of IPv6 address
|
||||
LocalString++;
|
||||
break;
|
||||
}
|
||||
else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) )
|
||||
{
|
||||
// port number is specified
|
||||
break;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
}
|
||||
|
||||
Result.m_Host = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
|
||||
// is port number specified?
|
||||
if ( *CurrentString == ':' )
|
||||
{
|
||||
CurrentString++;
|
||||
|
||||
// read port number
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString && *LocalString != '/' ) LocalString++;
|
||||
|
||||
Result.m_Port = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// end of string
|
||||
if ( !*CurrentString )
|
||||
{
|
||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// skip '/'
|
||||
if ( *CurrentString != '/' )
|
||||
{
|
||||
return clParseURL( LUrlParserError_NoSlash );
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
|
||||
// parse the path
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++;
|
||||
|
||||
Result.m_Path = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
|
||||
// check for query
|
||||
if ( *CurrentString == '?' )
|
||||
{
|
||||
// skip '?'
|
||||
CurrentString++;
|
||||
|
||||
// read query
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString && *LocalString != '#' ) LocalString++;
|
||||
|
||||
Result.m_Query = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// check for fragment
|
||||
if ( *CurrentString == '#' )
|
||||
{
|
||||
// skip '#'
|
||||
CurrentString++;
|
||||
|
||||
// read fragment
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString ) LocalString++;
|
||||
|
||||
Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString );
|
||||
}
|
||||
|
||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||
|
||||
return Result;
|
||||
}
|
78
ixwebsocket/LUrlParser.h
Normal file
78
ixwebsocket/LUrlParser.h
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
||||
* https://github.com/corporateshark/LUrlParser
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace LUrlParser
|
||||
{
|
||||
enum LUrlParserError
|
||||
{
|
||||
LUrlParserError_Ok = 0,
|
||||
LUrlParserError_Uninitialized = 1,
|
||||
LUrlParserError_NoUrlCharacter = 2,
|
||||
LUrlParserError_InvalidSchemeName = 3,
|
||||
LUrlParserError_NoDoubleSlash = 4,
|
||||
LUrlParserError_NoAtSign = 5,
|
||||
LUrlParserError_UnexpectedEndOfLine = 6,
|
||||
LUrlParserError_NoSlash = 7,
|
||||
};
|
||||
|
||||
class clParseURL
|
||||
{
|
||||
public:
|
||||
LUrlParserError m_ErrorCode;
|
||||
std::string m_Scheme;
|
||||
std::string m_Host;
|
||||
std::string m_Port;
|
||||
std::string m_Path;
|
||||
std::string m_Query;
|
||||
std::string m_Fragment;
|
||||
std::string m_UserName;
|
||||
std::string m_Password;
|
||||
|
||||
clParseURL()
|
||||
: m_ErrorCode( LUrlParserError_Uninitialized )
|
||||
{}
|
||||
|
||||
/// return 'true' if the parsing was successful
|
||||
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
|
||||
|
||||
/// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range)
|
||||
bool GetPort( int* OutPort ) const;
|
||||
|
||||
/// parse the URL
|
||||
static clParseURL ParseURL( const std::string& URL );
|
||||
|
||||
private:
|
||||
explicit clParseURL( LUrlParserError ErrorCode )
|
||||
: m_ErrorCode( ErrorCode )
|
||||
{}
|
||||
};
|
||||
|
||||
} // namespace LUrlParser
|
@ -4,13 +4,41 @@
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
#include "../IXSetThreadName.h"
|
||||
#include <iostream>
|
||||
#include <Windows.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
||||
|
||||
#pragma pack(push,8)
|
||||
typedef struct tagTHREADNAME_INFO
|
||||
{
|
||||
DWORD dwType; // Must be 0x1000.
|
||||
LPCSTR szName; // Pointer to name (in user addr space).
|
||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||
} THREADNAME_INFO;
|
||||
#pragma pack(pop)
|
||||
|
||||
void SetThreadName(DWORD dwThreadID, const char* threadName)
|
||||
{
|
||||
THREADNAME_INFO info;
|
||||
info.dwType = 0x1000;
|
||||
info.szName = threadName;
|
||||
info.dwThreadID = dwThreadID;
|
||||
info.dwFlags = 0;
|
||||
|
||||
__try
|
||||
{
|
||||
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)& info);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void setThreadName(const std::string& name)
|
||||
{
|
||||
// FIXME
|
||||
std::cerr << "setThreadName not implemented on Windows yet" << std::endl;
|
||||
SetThreadName(-1, name.c_str());
|
||||
}
|
||||
}
|
||||
|
7
test/.gitignore
vendored
7
test/.gitignore
vendored
@ -1,9 +1,10 @@
|
||||
CMakeCache.txt
|
||||
package-lock.json
|
||||
CMakeFiles
|
||||
ixwebsocket_unittest
|
||||
cmake_install.cmake
|
||||
CMakeFiles
|
||||
ixwebsocket_unittest
|
||||
cmake_install.cmake
|
||||
node_modules
|
||||
ixwebsocket
|
||||
Makefile
|
||||
build
|
||||
ixwebsocket_unittest.xml
|
||||
|
@ -5,15 +5,13 @@
|
||||
cmake_minimum_required (VERSION 3.4.1)
|
||||
project (ixwebsocket_unittest)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
|
||||
find_package(Sanitizers)
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||
set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||
|
||||
set (CMAKE_CXX_STANDARD 14)
|
||||
|
||||
if (NOT WIN32)
|
||||
if (UNIX)
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
|
||||
find_package(Sanitizers)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||
set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||
option(USE_TLS "Add TLS support" ON)
|
||||
endif()
|
||||
|
||||
@ -22,6 +20,8 @@ add_subdirectory(${PROJECT_SOURCE_DIR}/.. ixwebsocket)
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/Catch2/single_include
|
||||
../third_party/msgpack11
|
||||
../third_party/spdlog/include
|
||||
../ws
|
||||
)
|
||||
|
||||
# Shared sources
|
||||
@ -29,25 +29,33 @@ set (SOURCES
|
||||
test_runner.cpp
|
||||
IXTest.cpp
|
||||
../third_party/msgpack11/msgpack11.cpp
|
||||
../ws/ixcore/utils/IXCoreLogger.cpp
|
||||
|
||||
IXDNSLookupTest.cpp
|
||||
IXSocketTest.cpp
|
||||
IXSocketConnectTest.cpp
|
||||
IXWebSocketServerTest.cpp
|
||||
IXWebSocketPingTest.cpp
|
||||
IXWebSocketTestConnectionDisconnection.cpp
|
||||
IXUrlParserTest.cpp
|
||||
IXWebSocketServerTest.cpp
|
||||
IXWebSocketPingTest.cpp
|
||||
)
|
||||
|
||||
# Some unittest don't work on windows yet
|
||||
if (NOT WIN32)
|
||||
list(APPEND SOURCES
|
||||
IXWebSocketServerTest.cpp
|
||||
IXWebSocketPingTest.cpp
|
||||
IXWebSocketPingTimeoutTest.cpp
|
||||
if (UNIX)
|
||||
list(APPEND SOURCES
|
||||
# IXWebSocketPingTimeoutTest.cpp # This test isn't reliable # (multiple platforms), disabling in master
|
||||
# IXWebSocketCloseTest.cpp #
|
||||
cmd_websocket_chat.cpp
|
||||
IXWebSocketTestConnectionDisconnection.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
add_executable(ixwebsocket_unittest ${SOURCES})
|
||||
add_sanitizers(ixwebsocket_unittest)
|
||||
|
||||
if (UNIX)
|
||||
add_sanitizers(ixwebsocket_unittest)
|
||||
endif()
|
||||
|
||||
if (APPLE AND USE_TLS)
|
||||
target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")
|
||||
|
@ -108,7 +108,7 @@ namespace ix
|
||||
{
|
||||
log("Cannot compute a free port. bind error.");
|
||||
|
||||
::close(sockfd);
|
||||
Socket::closeSocket(sockfd);
|
||||
return getAnyFreePortRandom();
|
||||
}
|
||||
|
||||
@ -118,12 +118,12 @@ namespace ix
|
||||
{
|
||||
log("Cannot compute a free port. getsockname error.");
|
||||
|
||||
::close(sockfd);
|
||||
Socket::closeSocket(sockfd);
|
||||
return getAnyFreePortRandom();
|
||||
}
|
||||
|
||||
int port = ntohs(sa.sin_port);
|
||||
::close(sockfd);
|
||||
Socket::closeSocket(sockfd);
|
||||
|
||||
return port;
|
||||
}
|
||||
|
@ -27,15 +27,6 @@ namespace ix
|
||||
struct Logger
|
||||
{
|
||||
public:
|
||||
Logger& operator<<(const std::string& msg)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
std::cerr << msg;
|
||||
std::cerr << std::endl;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Logger& operator<<(T const& obj)
|
||||
{
|
||||
|
108
test/IXUrlParserTest.cpp
Normal file
108
test/IXUrlParserTest.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* IXSocketTest.cpp
|
||||
* Author: Korchynskyi Dmytro
|
||||
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <ixwebsocket/IXUrlParser.h>
|
||||
|
||||
#include "IXTest.h"
|
||||
#include "catch.hpp"
|
||||
#include <string.h>
|
||||
|
||||
using namespace ix;
|
||||
|
||||
namespace ix
|
||||
{
|
||||
|
||||
TEST_CASE("urlParser", "[urlParser]")
|
||||
{
|
||||
SECTION("http://google.com")
|
||||
{
|
||||
std::string url = "http://google.com";
|
||||
std::string protocol, host, path, query;
|
||||
int port;
|
||||
bool res;
|
||||
|
||||
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||
|
||||
REQUIRE(res);
|
||||
REQUIRE(protocol == "http");
|
||||
REQUIRE(host == "google.com");
|
||||
REQUIRE(path == "/");
|
||||
REQUIRE(query == "");
|
||||
REQUIRE(port == 80); // default port for http
|
||||
}
|
||||
|
||||
SECTION("https://google.com")
|
||||
{
|
||||
std::string url = "https://google.com";
|
||||
std::string protocol, host, path, query;
|
||||
int port;
|
||||
bool res;
|
||||
|
||||
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||
|
||||
REQUIRE(res);
|
||||
REQUIRE(protocol == "https");
|
||||
REQUIRE(host == "google.com");
|
||||
REQUIRE(path == "/");
|
||||
REQUIRE(query == "");
|
||||
REQUIRE(port == 443); // default port for https
|
||||
}
|
||||
|
||||
SECTION("ws://google.com")
|
||||
{
|
||||
std::string url = "ws://google.com";
|
||||
std::string protocol, host, path, query;
|
||||
int port;
|
||||
bool res;
|
||||
|
||||
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||
|
||||
REQUIRE(res);
|
||||
REQUIRE(protocol == "ws");
|
||||
REQUIRE(host == "google.com");
|
||||
REQUIRE(path == "/");
|
||||
REQUIRE(query == "");
|
||||
REQUIRE(port == 80); // default port for ws
|
||||
}
|
||||
|
||||
SECTION("wss://google.com/ws?arg=value")
|
||||
{
|
||||
std::string url = "wss://google.com/ws?arg=value&arg2=value2";
|
||||
std::string protocol, host, path, query;
|
||||
int port;
|
||||
bool res;
|
||||
|
||||
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||
|
||||
REQUIRE(res);
|
||||
REQUIRE(protocol == "wss");
|
||||
REQUIRE(host == "google.com");
|
||||
REQUIRE(path == "/ws?arg=value&arg2=value2");
|
||||
REQUIRE(query == "arg=value&arg2=value2");
|
||||
REQUIRE(port == 443); // default port for wss
|
||||
}
|
||||
|
||||
SECTION("real test")
|
||||
{
|
||||
std::string url = "ws://127.0.0.1:7350/ws?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNWQxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&status=true&format=protobuf";
|
||||
std::string protocol, host, path, query;
|
||||
int port;
|
||||
bool res;
|
||||
|
||||
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||
|
||||
REQUIRE(res);
|
||||
REQUIRE(protocol == "ws");
|
||||
REQUIRE(host == "127.0.0.1");
|
||||
REQUIRE(path == "/ws?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNWQxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&status=true&format=protobuf");
|
||||
REQUIRE(query == "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNWQxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&status=true&format=protobuf");
|
||||
REQUIRE(port == 7350);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
407
test/IXWebSocketCloseTest.cpp
Normal file
407
test/IXWebSocketCloseTest.cpp
Normal file
@ -0,0 +1,407 @@
|
||||
/*
|
||||
* IXWebSocketCloseTest.cpp
|
||||
* Author: Alexandre Konieczny
|
||||
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <queue>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixwebsocket/IXWebSocketServer.h>
|
||||
|
||||
#include "IXTest.h"
|
||||
|
||||
#include "catch.hpp"
|
||||
|
||||
using namespace ix;
|
||||
|
||||
namespace
|
||||
{
|
||||
class WebSocketClient
|
||||
{
|
||||
public:
|
||||
WebSocketClient(int port);
|
||||
|
||||
void subscribe(const std::string& channel);
|
||||
void start();
|
||||
void stop();
|
||||
void stop(uint16_t code, const std::string& reason);
|
||||
bool isReady() const;
|
||||
void sendMessage(const std::string& text);
|
||||
|
||||
uint16_t getCloseCode();
|
||||
const std::string& getCloseReason();
|
||||
bool getCloseRemote();
|
||||
|
||||
private:
|
||||
ix::WebSocket _webSocket;
|
||||
int _port;
|
||||
|
||||
mutable std::mutex _mutexCloseData;
|
||||
uint16_t _closeCode;
|
||||
std::string _closeReason;
|
||||
bool _closeRemote;
|
||||
};
|
||||
|
||||
WebSocketClient::WebSocketClient(int port)
|
||||
: _port(port)
|
||||
, _closeCode(0)
|
||||
, _closeReason(std::string(""))
|
||||
, _closeRemote(false)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
bool WebSocketClient::isReady() const
|
||||
{
|
||||
return _webSocket.getReadyState() == ix::ReadyState::Open;
|
||||
}
|
||||
|
||||
uint16_t WebSocketClient::getCloseCode()
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(_mutexCloseData);
|
||||
|
||||
return _closeCode;
|
||||
}
|
||||
|
||||
const std::string& WebSocketClient::getCloseReason()
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(_mutexCloseData);
|
||||
|
||||
return _closeReason;
|
||||
}
|
||||
|
||||
bool WebSocketClient::getCloseRemote()
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(_mutexCloseData);
|
||||
|
||||
return _closeRemote;
|
||||
}
|
||||
|
||||
void WebSocketClient::stop()
|
||||
{
|
||||
_webSocket.stop();
|
||||
}
|
||||
|
||||
void WebSocketClient::stop(uint16_t code, const std::string& reason)
|
||||
{
|
||||
_webSocket.close(code, reason);
|
||||
_webSocket.stop();
|
||||
}
|
||||
|
||||
void WebSocketClient::start()
|
||||
{
|
||||
std::string url;
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "ws://localhost:"
|
||||
<< _port
|
||||
<< "/";
|
||||
|
||||
url = ss.str();
|
||||
}
|
||||
|
||||
_webSocket.setUrl(url);
|
||||
|
||||
std::stringstream ss;
|
||||
log(std::string("Connecting to url: ") + url);
|
||||
|
||||
_webSocket.setOnMessageCallback(
|
||||
[this](ix::WebSocketMessageType messageType,
|
||||
const std::string& str,
|
||||
size_t wireSize,
|
||||
const ix::WebSocketErrorInfo& error,
|
||||
const ix::WebSocketOpenInfo& openInfo,
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (messageType == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
log("client connected");
|
||||
|
||||
_webSocket.disableAutomaticReconnection();
|
||||
}
|
||||
else if (messageType == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
log("client disconnected");
|
||||
|
||||
std::lock_guard<std::mutex> lck(_mutexCloseData);
|
||||
|
||||
_closeCode = closeInfo.code;
|
||||
_closeReason = std::string(closeInfo.reason);
|
||||
_closeRemote = closeInfo.remote;
|
||||
|
||||
_webSocket.disableAutomaticReconnection();
|
||||
}
|
||||
else if (messageType == ix::WebSocketMessageType::Error)
|
||||
{
|
||||
ss << "Error ! " << error.reason;
|
||||
log(ss.str());
|
||||
|
||||
_webSocket.disableAutomaticReconnection();
|
||||
}
|
||||
else if (messageType == ix::WebSocketMessageType::Pong)
|
||||
{
|
||||
ss << "Received pong message " << str;
|
||||
log(ss.str());
|
||||
}
|
||||
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||
{
|
||||
ss << "Received ping message " << str;
|
||||
log(ss.str());
|
||||
}
|
||||
else if (messageType == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
ss << "Received message " << str;
|
||||
log(ss.str());
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "Invalid ix::WebSocketMessageType";
|
||||
log(ss.str());
|
||||
}
|
||||
});
|
||||
|
||||
_webSocket.start();
|
||||
}
|
||||
|
||||
void WebSocketClient::sendMessage(const std::string& text)
|
||||
{
|
||||
_webSocket.send(text);
|
||||
}
|
||||
|
||||
bool startServer(ix::WebSocketServer& server,
|
||||
uint16_t& receivedCloseCode,
|
||||
std::string& receivedCloseReason,
|
||||
bool& receivedCloseRemote,
|
||||
std::mutex& mutexWrite)
|
||||
{
|
||||
// A dev/null server
|
||||
server.setOnConnectionCallback(
|
||||
[&server, &receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](std::shared_ptr<ix::WebSocket> webSocket,
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
webSocket->setOnMessageCallback(
|
||||
[webSocket, connectionState, &server, &receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](ix::WebSocketMessageType messageType,
|
||||
const std::string& str,
|
||||
size_t wireSize,
|
||||
const ix::WebSocketErrorInfo& error,
|
||||
const ix::WebSocketOpenInfo& openInfo,
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
if (messageType == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
Logger() << "New server connection";
|
||||
Logger() << "id: " << connectionState->getId();
|
||||
Logger() << "Uri: " << openInfo.uri;
|
||||
Logger() << "Headers:";
|
||||
for (auto it : openInfo.headers)
|
||||
{
|
||||
Logger() << it.first << ": " << it.second;
|
||||
}
|
||||
}
|
||||
else if (messageType == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
log("Server closed connection");
|
||||
|
||||
//Logger() << closeInfo.code;
|
||||
//Logger() << closeInfo.reason;
|
||||
//Logger() << closeInfo.remote;
|
||||
|
||||
std::lock_guard<std::mutex> lck(mutexWrite);
|
||||
|
||||
receivedCloseCode = closeInfo.code;
|
||||
receivedCloseReason = std::string(closeInfo.reason);
|
||||
receivedCloseRemote = closeInfo.remote;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
auto res = server.listen();
|
||||
if (!res.first)
|
||||
{
|
||||
log(res.second);
|
||||
return false;
|
||||
}
|
||||
|
||||
server.start();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Websocket_client_close_default", "[close]")
|
||||
{
|
||||
SECTION("Make sure that close code and reason was used and sent to server.")
|
||||
{
|
||||
ix::setupWebSocketTrafficTrackerCallback();
|
||||
|
||||
int port = getFreePort();
|
||||
ix::WebSocketServer server(port);
|
||||
|
||||
uint16_t serverReceivedCloseCode(0);
|
||||
bool serverReceivedCloseRemote(false);
|
||||
std::string serverReceivedCloseReason("");
|
||||
std::mutex mutexWrite;
|
||||
|
||||
REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite));
|
||||
|
||||
std::string session = ix::generateSessionId();
|
||||
WebSocketClient webSocketClient(port);
|
||||
|
||||
webSocketClient.start();
|
||||
|
||||
// Wait for all chat instance to be ready
|
||||
while (true)
|
||||
{
|
||||
if (webSocketClient.isReady()) break;
|
||||
ix::msleep(10);
|
||||
}
|
||||
|
||||
REQUIRE(server.getClients().size() == 1);
|
||||
|
||||
ix::msleep(100);
|
||||
|
||||
webSocketClient.stop();
|
||||
|
||||
ix::msleep(200);
|
||||
|
||||
// ensure client close is the same as values given
|
||||
REQUIRE(webSocketClient.getCloseCode() == 1000);
|
||||
REQUIRE(webSocketClient.getCloseReason() == "Normal closure");
|
||||
REQUIRE(webSocketClient.getCloseRemote() == false);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(mutexWrite);
|
||||
|
||||
// Here we read the code/reason received by the server, and ensure that remote is true
|
||||
REQUIRE(serverReceivedCloseCode == 1000);
|
||||
REQUIRE(serverReceivedCloseReason == "Normal closure");
|
||||
REQUIRE(serverReceivedCloseRemote == true);
|
||||
}
|
||||
|
||||
// Give us 1000ms for the server to notice that clients went away
|
||||
ix::msleep(1000);
|
||||
REQUIRE(server.getClients().size() == 0);
|
||||
|
||||
ix::reportWebSocketTraffic();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Websocket_client_close_params_given", "[close]")
|
||||
{
|
||||
SECTION("Make sure that close code and reason was used and sent to server.")
|
||||
{
|
||||
ix::setupWebSocketTrafficTrackerCallback();
|
||||
|
||||
int port = getFreePort();
|
||||
ix::WebSocketServer server(port);
|
||||
|
||||
uint16_t serverReceivedCloseCode(0);
|
||||
bool serverReceivedCloseRemote(false);
|
||||
std::string serverReceivedCloseReason("");
|
||||
std::mutex mutexWrite;
|
||||
|
||||
REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite));
|
||||
|
||||
std::string session = ix::generateSessionId();
|
||||
WebSocketClient webSocketClient(port);
|
||||
|
||||
webSocketClient.start();
|
||||
|
||||
// Wait for all chat instance to be ready
|
||||
while (true)
|
||||
{
|
||||
if (webSocketClient.isReady()) break;
|
||||
ix::msleep(10);
|
||||
}
|
||||
|
||||
REQUIRE(server.getClients().size() == 1);
|
||||
|
||||
ix::msleep(100);
|
||||
|
||||
webSocketClient.stop(4000, "My reason");
|
||||
|
||||
ix::msleep(500);
|
||||
|
||||
// ensure client close is the same as values given
|
||||
REQUIRE(webSocketClient.getCloseCode() == 4000);
|
||||
REQUIRE(webSocketClient.getCloseReason() == "My reason");
|
||||
REQUIRE(webSocketClient.getCloseRemote() == false);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(mutexWrite);
|
||||
|
||||
// Here we read the code/reason received by the server, and ensure that remote is true
|
||||
REQUIRE(serverReceivedCloseCode == 4000);
|
||||
REQUIRE(serverReceivedCloseReason == "My reason");
|
||||
REQUIRE(serverReceivedCloseRemote == true);
|
||||
}
|
||||
|
||||
// Give us 1000ms for the server to notice that clients went away
|
||||
ix::msleep(1000);
|
||||
REQUIRE(server.getClients().size() == 0);
|
||||
|
||||
ix::reportWebSocketTraffic();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Websocket_server_close", "[close]")
|
||||
{
|
||||
SECTION("Make sure that close code and reason was read from server.")
|
||||
{
|
||||
ix::setupWebSocketTrafficTrackerCallback();
|
||||
|
||||
int port = getFreePort();
|
||||
ix::WebSocketServer server(port);
|
||||
|
||||
uint16_t serverReceivedCloseCode(0);
|
||||
bool serverReceivedCloseRemote(false);
|
||||
std::string serverReceivedCloseReason("");
|
||||
std::mutex mutexWrite;
|
||||
|
||||
REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite));
|
||||
|
||||
std::string session = ix::generateSessionId();
|
||||
WebSocketClient webSocketClient(port);
|
||||
|
||||
webSocketClient.start();
|
||||
|
||||
// Wait for all chat instance to be ready
|
||||
while (true)
|
||||
{
|
||||
if (webSocketClient.isReady()) break;
|
||||
ix::msleep(10);
|
||||
}
|
||||
|
||||
REQUIRE(server.getClients().size() == 1);
|
||||
|
||||
ix::msleep(200);
|
||||
|
||||
server.stop();
|
||||
|
||||
ix::msleep(500);
|
||||
|
||||
// ensure client close is the same as values given
|
||||
REQUIRE(webSocketClient.getCloseCode() == 1000);
|
||||
REQUIRE(webSocketClient.getCloseReason() == "Normal closure");
|
||||
REQUIRE(webSocketClient.getCloseRemote() == true);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(mutexWrite);
|
||||
|
||||
// Here we read the code/reason received by the server, and ensure that remote is true
|
||||
REQUIRE(serverReceivedCloseCode == 1000);
|
||||
REQUIRE(serverReceivedCloseReason == "Normal closure");
|
||||
REQUIRE(serverReceivedCloseRemote == false);
|
||||
}
|
||||
|
||||
// Give us 1000ms for the server to notice that clients went away
|
||||
ix::msleep(1000);
|
||||
REQUIRE(server.getClients().size() == 0);
|
||||
|
||||
ix::reportWebSocketTraffic();
|
||||
}
|
||||
}
|
@ -23,7 +23,6 @@ namespace
|
||||
public:
|
||||
WebSocketClient(int port, bool useHeartBeatMethod);
|
||||
|
||||
void subscribe(const std::string& channel);
|
||||
void start();
|
||||
void stop();
|
||||
bool isReady() const;
|
||||
@ -44,7 +43,7 @@ namespace
|
||||
|
||||
bool WebSocketClient::isReady() const
|
||||
{
|
||||
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
|
||||
return _webSocket.getReadyState() == ix::ReadyState::Open;
|
||||
}
|
||||
|
||||
void WebSocketClient::stop()
|
||||
@ -57,7 +56,7 @@ namespace
|
||||
std::string url;
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "ws://localhost:"
|
||||
ss << "ws://127.0.0.1:"
|
||||
<< _port
|
||||
<< "/";
|
||||
|
||||
@ -69,9 +68,13 @@ namespace
|
||||
// The important bit for this test.
|
||||
// Set a 1 second heartbeat with the setter method to test
|
||||
if (_useHeartBeatMethod)
|
||||
{
|
||||
_webSocket.setHeartBeatPeriod(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_webSocket.setPingInterval(1);
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
log(std::string("Connecting to url: ") + url);
|
||||
@ -85,33 +88,32 @@ namespace
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (messageType == ix::WebSocket_MessageType_Open)
|
||||
if (messageType == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
log("client connected");
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||
else if (messageType == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
log("client disconnected");
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Error)
|
||||
else if (messageType == ix::WebSocketMessageType::Error)
|
||||
{
|
||||
ss << "Error ! " << error.reason;
|
||||
log(ss.str());
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Pong)
|
||||
else if (messageType == ix::WebSocketMessageType::Pong)
|
||||
{
|
||||
ss << "Received pong message " << str;
|
||||
log(ss.str());
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Ping)
|
||||
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||
{
|
||||
ss << "Received ping message " << str;
|
||||
log(ss.str());
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
||||
else if (messageType == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
ss << "Received message " << str;
|
||||
log(ss.str());
|
||||
// too many messages to log
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -143,7 +145,7 @@ namespace
|
||||
const ix::WebSocketOpenInfo& openInfo,
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
if (messageType == ix::WebSocket_MessageType_Open)
|
||||
if (messageType == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
Logger() << "New server connection";
|
||||
Logger() << "id: " << connectionState->getId();
|
||||
@ -154,15 +156,23 @@ namespace
|
||||
Logger() << it.first << ": " << it.second;
|
||||
}
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||
else if (messageType == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
log("Server closed connection");
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Ping)
|
||||
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||
{
|
||||
log("Server received a ping");
|
||||
receivedPingMessages++;
|
||||
}
|
||||
else if (messageType == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
// to many messages to log
|
||||
for(auto client: server.getClients())
|
||||
{
|
||||
client->sendText("reply");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -180,96 +190,6 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
|
||||
{
|
||||
SECTION("Make sure that ping messages are sent when no other data are sent.")
|
||||
{
|
||||
ix::setupWebSocketTrafficTrackerCallback();
|
||||
|
||||
int port = getFreePort();
|
||||
ix::WebSocketServer server(port);
|
||||
std::atomic<int> serverReceivedPingMessages(0);
|
||||
REQUIRE(startServer(server, serverReceivedPingMessages));
|
||||
|
||||
std::string session = ix::generateSessionId();
|
||||
bool useSetHeartBeatPeriodMethod = true;
|
||||
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
|
||||
|
||||
webSocketClient.start();
|
||||
|
||||
// Wait for all chat instance to be ready
|
||||
while (true)
|
||||
{
|
||||
if (webSocketClient.isReady()) break;
|
||||
ix::msleep(10);
|
||||
}
|
||||
|
||||
REQUIRE(server.getClients().size() == 1);
|
||||
|
||||
ix::msleep(1900);
|
||||
|
||||
webSocketClient.stop();
|
||||
|
||||
|
||||
// Here we test ping interval
|
||||
// -> expected ping messages == 1 as 1900 seconds, 1 ping sent every second
|
||||
REQUIRE(serverReceivedPingMessages == 1);
|
||||
|
||||
// Give us 500ms for the server to notice that clients went away
|
||||
ix::msleep(500);
|
||||
REQUIRE(server.getClients().size() == 0);
|
||||
|
||||
ix::reportWebSocketTraffic();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
|
||||
{
|
||||
SECTION("Make sure that ping messages are sent, even if other messages are sent")
|
||||
{
|
||||
ix::setupWebSocketTrafficTrackerCallback();
|
||||
|
||||
int port = getFreePort();
|
||||
ix::WebSocketServer server(port);
|
||||
std::atomic<int> serverReceivedPingMessages(0);
|
||||
REQUIRE(startServer(server, serverReceivedPingMessages));
|
||||
|
||||
std::string session = ix::generateSessionId();
|
||||
bool useSetHeartBeatPeriodMethod = true;
|
||||
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
|
||||
|
||||
webSocketClient.start();
|
||||
|
||||
// Wait for all chat instance to be ready
|
||||
while (true)
|
||||
{
|
||||
if (webSocketClient.isReady()) break;
|
||||
ix::msleep(10);
|
||||
}
|
||||
|
||||
REQUIRE(server.getClients().size() == 1);
|
||||
|
||||
ix::msleep(900);
|
||||
webSocketClient.sendMessage("hello world");
|
||||
ix::msleep(900);
|
||||
webSocketClient.sendMessage("hello world");
|
||||
ix::msleep(1100);
|
||||
|
||||
webSocketClient.stop();
|
||||
|
||||
// Here we test ping interval
|
||||
// client has sent data, but ping should have been sent no matter what
|
||||
// -> expected ping messages == 2 as 900+900+1100 = 2900 seconds, 1 ping sent every second
|
||||
REQUIRE(serverReceivedPingMessages == 2);
|
||||
|
||||
// Give us 500ms for the server to notice that clients went away
|
||||
ix::msleep(500);
|
||||
REQUIRE(server.getClients().size() == 0);
|
||||
|
||||
ix::reportWebSocketTraffic();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Websocket_ping_no_data_sent_setPingInterval", "[setPingInterval]")
|
||||
{
|
||||
SECTION("Make sure that ping messages are sent when no other data are sent.")
|
||||
@ -359,3 +279,203 @@ TEST_CASE("Websocket_ping_data_sent_setPingInterval", "[setPingInterval]")
|
||||
ix::reportWebSocketTraffic();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Websocket_ping_data_sent_setPingInterval_half_full", "[setPingInterval]")
|
||||
{
|
||||
SECTION("Make sure that ping messages are sent, even if other messages are sent continuously during a given time")
|
||||
{
|
||||
ix::setupWebSocketTrafficTrackerCallback();
|
||||
|
||||
int port = getFreePort();
|
||||
ix::WebSocketServer server(port);
|
||||
std::atomic<int> serverReceivedPingMessages(0);
|
||||
REQUIRE(startServer(server, serverReceivedPingMessages));
|
||||
|
||||
std::string session = ix::generateSessionId();
|
||||
bool useSetHeartBeatPeriodMethod = false; // so use setPingInterval
|
||||
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
|
||||
|
||||
webSocketClient.start();
|
||||
|
||||
// Wait for all chat instance to be ready
|
||||
while (true)
|
||||
{
|
||||
if (webSocketClient.isReady()) break;
|
||||
ix::msleep(10);
|
||||
}
|
||||
|
||||
REQUIRE(server.getClients().size() == 1);
|
||||
|
||||
// send continuously for 1100ms
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
while(std::chrono::steady_clock::now() - now <= std::chrono::milliseconds(900))
|
||||
{
|
||||
webSocketClient.sendMessage("message");
|
||||
ix::msleep(1);
|
||||
}
|
||||
ix::msleep(150);
|
||||
|
||||
// Here we test ping interval
|
||||
// client has sent data, but ping should have been sent no matter what
|
||||
// -> expected ping messages == 1, as 900+150 = 1050ms, 1 ping sent every second
|
||||
REQUIRE(serverReceivedPingMessages == 1);
|
||||
|
||||
ix::msleep(100);
|
||||
|
||||
webSocketClient.stop();
|
||||
|
||||
// Give us 500ms for the server to notice that clients went away
|
||||
ix::msleep(500);
|
||||
REQUIRE(server.getClients().size() == 0);
|
||||
|
||||
ix::reportWebSocketTraffic();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Websocket_ping_data_sent_setPingInterval_full", "[setPingInterval]")
|
||||
{
|
||||
SECTION("Make sure that ping messages are sent, even if other messages are sent continuously for longer than ping interval")
|
||||
{
|
||||
ix::setupWebSocketTrafficTrackerCallback();
|
||||
|
||||
int port = getFreePort();
|
||||
ix::WebSocketServer server(port);
|
||||
std::atomic<int> serverReceivedPingMessages(0);
|
||||
REQUIRE(startServer(server, serverReceivedPingMessages));
|
||||
|
||||
std::string session = ix::generateSessionId();
|
||||
bool useSetHeartBeatPeriodMethod = false; // so use setPingInterval
|
||||
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
|
||||
|
||||
webSocketClient.start();
|
||||
|
||||
// Wait for all chat instance to be ready
|
||||
while (true)
|
||||
{
|
||||
if (webSocketClient.isReady()) break;
|
||||
ix::msleep(1);
|
||||
}
|
||||
|
||||
REQUIRE(server.getClients().size() == 1);
|
||||
|
||||
// send continuously for 1100ms
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
while(std::chrono::steady_clock::now() - now <= std::chrono::milliseconds(1100))
|
||||
{
|
||||
webSocketClient.sendMessage("message");
|
||||
ix::msleep(1);
|
||||
}
|
||||
|
||||
// Here we test ping interval
|
||||
// client has sent data, but ping should have been sent no matter what
|
||||
// -> expected ping messages == 1, 1 ping sent every second
|
||||
REQUIRE(serverReceivedPingMessages == 1);
|
||||
|
||||
ix::msleep(100);
|
||||
|
||||
webSocketClient.stop();
|
||||
|
||||
// Give us 500ms for the server to notice that clients went away
|
||||
ix::msleep(500);
|
||||
REQUIRE(server.getClients().size() == 0);
|
||||
|
||||
ix::reportWebSocketTraffic();
|
||||
}
|
||||
}
|
||||
|
||||
// Using setHeartBeatPeriod
|
||||
|
||||
TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
|
||||
{
|
||||
SECTION("Make sure that ping messages are sent when no other data are sent.")
|
||||
{
|
||||
ix::setupWebSocketTrafficTrackerCallback();
|
||||
|
||||
int port = getFreePort();
|
||||
ix::WebSocketServer server(port);
|
||||
std::atomic<int> serverReceivedPingMessages(0);
|
||||
REQUIRE(startServer(server, serverReceivedPingMessages));
|
||||
|
||||
std::string session = ix::generateSessionId();
|
||||
bool useSetHeartBeatPeriodMethod = true;
|
||||
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
|
||||
|
||||
webSocketClient.start();
|
||||
|
||||
// Wait for all chat instance to be ready
|
||||
while (true)
|
||||
{
|
||||
if (webSocketClient.isReady()) break;
|
||||
ix::msleep(1);
|
||||
}
|
||||
|
||||
REQUIRE(server.getClients().size() == 1);
|
||||
|
||||
ix::msleep(1900);
|
||||
|
||||
webSocketClient.stop();
|
||||
|
||||
|
||||
// Here we test ping interval
|
||||
// -> expected ping messages == 1 as 1900 seconds, 1 ping sent every second
|
||||
REQUIRE(serverReceivedPingMessages == 1);
|
||||
|
||||
// Give us 500ms for the server to notice that clients went away
|
||||
ix::msleep(500);
|
||||
REQUIRE(server.getClients().size() == 0);
|
||||
|
||||
ix::reportWebSocketTraffic();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
|
||||
{
|
||||
SECTION("Make sure that ping messages are sent, even if other messages are sent")
|
||||
{
|
||||
ix::setupWebSocketTrafficTrackerCallback();
|
||||
|
||||
int port = getFreePort();
|
||||
ix::WebSocketServer server(port);
|
||||
std::atomic<int> serverReceivedPingMessages(0);
|
||||
REQUIRE(startServer(server, serverReceivedPingMessages));
|
||||
|
||||
std::string session = ix::generateSessionId();
|
||||
bool useSetHeartBeatPeriodMethod = true;
|
||||
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
|
||||
|
||||
webSocketClient.start();
|
||||
|
||||
// Wait for all chat instance to be ready
|
||||
while (true)
|
||||
{
|
||||
if (webSocketClient.isReady()) break;
|
||||
ix::msleep(1);
|
||||
}
|
||||
|
||||
REQUIRE(server.getClients().size() == 1);
|
||||
|
||||
ix::msleep(900);
|
||||
webSocketClient.sendMessage("hello world");
|
||||
ix::msleep(900);
|
||||
webSocketClient.sendMessage("hello world");
|
||||
ix::msleep(1100);
|
||||
|
||||
webSocketClient.stop();
|
||||
|
||||
// without this sleep test fails on Windows
|
||||
ix::msleep(100);
|
||||
|
||||
// Here we test ping interval
|
||||
// client has sent data, but ping should have been sent no matter what
|
||||
// -> expected ping messages == 2 as 900+900+1100 = 2900 seconds, 1 ping sent every second
|
||||
REQUIRE(serverReceivedPingMessages == 2);
|
||||
|
||||
// Give us 500ms for the server to notice that clients went away
|
||||
ix::msleep(500);
|
||||
REQUIRE(server.getClients().size() == 0);
|
||||
|
||||
ix::reportWebSocketTraffic();
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ namespace
|
||||
public:
|
||||
WebSocketClient(int port, int pingInterval, int pingTimeout);
|
||||
|
||||
void subscribe(const std::string& channel);
|
||||
void start();
|
||||
void stop();
|
||||
bool isReady() const;
|
||||
@ -53,12 +52,12 @@ namespace
|
||||
|
||||
bool WebSocketClient::isReady() const
|
||||
{
|
||||
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
|
||||
return _webSocket.getReadyState() == ix::ReadyState::Open;
|
||||
}
|
||||
|
||||
bool WebSocketClient::isClosed() const
|
||||
{
|
||||
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Closed;
|
||||
return _webSocket.getReadyState() == ix::ReadyState::Closed;
|
||||
}
|
||||
|
||||
void WebSocketClient::stop()
|
||||
@ -71,7 +70,7 @@ namespace
|
||||
std::string url;
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "ws://localhost:"
|
||||
ss << "ws://127.0.0.1:"
|
||||
<< _port
|
||||
<< "/";
|
||||
|
||||
@ -79,6 +78,7 @@ namespace
|
||||
}
|
||||
|
||||
_webSocket.setUrl(url);
|
||||
_webSocket.disableAutomaticReconnection();
|
||||
|
||||
// The important bit for this test.
|
||||
// Set a ping interval, and a ping timeout
|
||||
@ -97,13 +97,12 @@ namespace
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (messageType == ix::WebSocket_MessageType_Open)
|
||||
if (messageType == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
log("client connected");
|
||||
|
||||
_webSocket.disableAutomaticReconnection();
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||
else if (messageType == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
log("client disconnected");
|
||||
|
||||
@ -112,29 +111,25 @@ namespace
|
||||
_closedDueToPingTimeout = true;
|
||||
}
|
||||
|
||||
_webSocket.disableAutomaticReconnection();
|
||||
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Error)
|
||||
else if (messageType == ix::WebSocketMessageType::Error)
|
||||
{
|
||||
ss << "Error ! " << error.reason;
|
||||
log(ss.str());
|
||||
|
||||
_webSocket.disableAutomaticReconnection();
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Pong)
|
||||
else if (messageType == ix::WebSocketMessageType::Pong)
|
||||
{
|
||||
_receivedPongMessages++;
|
||||
|
||||
ss << "Received pong message " << str;
|
||||
log(ss.str());
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Ping)
|
||||
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||
{
|
||||
ss << "Received ping message " << str;
|
||||
log(ss.str());
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
||||
else if (messageType == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
ss << "Received message " << str;
|
||||
log(ss.str());
|
||||
@ -179,7 +174,7 @@ namespace
|
||||
const ix::WebSocketOpenInfo& openInfo,
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
if (messageType == ix::WebSocket_MessageType_Open)
|
||||
if (messageType == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
Logger() << "New server connection";
|
||||
Logger() << "id: " << connectionState->getId();
|
||||
@ -190,11 +185,11 @@ namespace
|
||||
Logger() << it.first << ": " << it.second;
|
||||
}
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||
else if (messageType == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
log("Server closed connection");
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Ping)
|
||||
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||
{
|
||||
log("Server received a ping");
|
||||
receivedPingMessages++;
|
||||
@ -415,7 +410,7 @@ TEST_CASE("Websocket_ping_timeout", "[setPingTimeout]")
|
||||
REQUIRE(serverReceivedPingMessages == 1);
|
||||
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
|
||||
|
||||
ix::msleep(1000);
|
||||
ix::msleep(1100);
|
||||
|
||||
// Here we test ping timeout, timeout
|
||||
REQUIRE(serverReceivedPingMessages == 1);
|
||||
|
@ -50,7 +50,7 @@ namespace ix
|
||||
const ix::WebSocketOpenInfo& openInfo,
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
if (messageType == ix::WebSocket_MessageType_Open)
|
||||
if (messageType == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
Logger() << "New connection";
|
||||
connectionState->computeId();
|
||||
@ -64,11 +64,11 @@ namespace ix
|
||||
|
||||
connectionId = connectionState->getId();
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||
else if (messageType == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
Logger() << "Closed connection";
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
||||
else if (messageType == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
for (auto&& client : server.getClients())
|
||||
{
|
||||
@ -107,7 +107,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
||||
std::string errMsg;
|
||||
bool tls = false;
|
||||
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
||||
std::string host("localhost");
|
||||
std::string host("127.0.0.1");
|
||||
auto isCancellationRequested = []() -> bool
|
||||
{
|
||||
return false;
|
||||
@ -141,7 +141,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
||||
std::string errMsg;
|
||||
bool tls = false;
|
||||
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
||||
std::string host("localhost");
|
||||
std::string host("127.0.0.1");
|
||||
auto isCancellationRequested = []() -> bool
|
||||
{
|
||||
return false;
|
||||
@ -178,7 +178,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
||||
std::string errMsg;
|
||||
bool tls = false;
|
||||
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
||||
std::string host("localhost");
|
||||
std::string host("127.0.0.1");
|
||||
auto isCancellationRequested = []() -> bool
|
||||
{
|
||||
return false;
|
||||
|
@ -60,30 +60,36 @@ namespace
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (messageType == ix::WebSocket_MessageType_Open)
|
||||
if (messageType == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
log("cmd_websocket_satori_chat: connected !");
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||
else if (messageType == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
log("cmd_websocket_satori_chat: disconnected !");
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Error)
|
||||
else if (messageType == ix::WebSocketMessageType::Error)
|
||||
{
|
||||
log("cmd_websocket_satori_chat: Error!");
|
||||
ss << "cmd_websocket_satori_chat: Error! ";
|
||||
ss << error.reason;
|
||||
log(ss.str());
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
||||
else if (messageType == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
log("cmd_websocket_satori_chat: received message.!");
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Ping)
|
||||
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||
{
|
||||
log("cmd_websocket_satori_chat: received ping message.!");
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Pong)
|
||||
else if (messageType == ix::WebSocketMessageType::Pong)
|
||||
{
|
||||
log("cmd_websocket_satori_chat: received pong message.!");
|
||||
}
|
||||
else if (messageType == ix::WebSocketMessageType::Fragment)
|
||||
{
|
||||
log("cmd_websocket_satori_chat: received fragment.!");
|
||||
}
|
||||
else
|
||||
{
|
||||
log("Invalid ix::WebSocketMessageType");
|
||||
@ -103,26 +109,40 @@ TEST_CASE("websocket_connections", "[websocket]")
|
||||
{
|
||||
SECTION("Try to connect to invalid servers.")
|
||||
{
|
||||
IXWebSocketTestConnectionDisconnection chatA;
|
||||
IXWebSocketTestConnectionDisconnection test;
|
||||
|
||||
chatA.start(GOOGLE_URL);
|
||||
test.start(GOOGLE_URL);
|
||||
ix::msleep(1000);
|
||||
chatA.stop();
|
||||
test.stop();
|
||||
|
||||
chatA.start(UNKNOWN_URL);
|
||||
test.start(UNKNOWN_URL);
|
||||
ix::msleep(1000);
|
||||
chatA.stop();
|
||||
test.stop();
|
||||
}
|
||||
|
||||
SECTION("Try to connect and disconnect with different timing.")
|
||||
SECTION("Try to connect and disconnect with different timing, not enough time to succesfully connect")
|
||||
{
|
||||
IXWebSocketTestConnectionDisconnection chatA;
|
||||
IXWebSocketTestConnectionDisconnection test;
|
||||
for (int i = 0; i < 50; ++i)
|
||||
{
|
||||
log(std::string("Run: ") + std::to_string(i));
|
||||
chatA.start(WEBSOCKET_DOT_ORG_URL);
|
||||
test.start(WEBSOCKET_DOT_ORG_URL);
|
||||
ix::msleep(i);
|
||||
chatA.stop();
|
||||
test.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// This test breaks on travis CI - Ubuntu Xenial + gcc + tsan
|
||||
// We should fix this.
|
||||
/*SECTION("Try to connect and disconnect with different timing, from not enough time to successfull connect")
|
||||
{
|
||||
IXWebSocketTestConnectionDisconnection test;
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
log(std::string("Run: ") + std::to_string(i));
|
||||
test.start(WEBSOCKET_DOT_ORG_URL);
|
||||
ix::msleep(i*50);
|
||||
test.stop();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ namespace
|
||||
|
||||
bool WebSocketChat::isReady() const
|
||||
{
|
||||
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
|
||||
return _webSocket.getReadyState() == ix::ReadyState::Open;
|
||||
}
|
||||
|
||||
void WebSocketChat::stop()
|
||||
@ -100,7 +100,7 @@ namespace
|
||||
std::string url;
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "ws://localhost:"
|
||||
ss << "ws://127.0.0.1:"
|
||||
<< _port
|
||||
<< "/"
|
||||
<< _user;
|
||||
@ -122,21 +122,21 @@ namespace
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (messageType == ix::WebSocket_MessageType_Open)
|
||||
if (messageType == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
ss << "cmd_websocket_chat: user "
|
||||
<< _user
|
||||
<< " Connected !";
|
||||
log(ss.str());
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||
else if (messageType == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
ss << "cmd_websocket_chat: user "
|
||||
<< _user
|
||||
<< " disconnected !";
|
||||
log(ss.str());
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
||||
else if (messageType == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
auto result = decodeMessage(str);
|
||||
|
||||
@ -159,20 +159,20 @@ namespace
|
||||
<< _user << " > ";
|
||||
log(ss.str());
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Error)
|
||||
else if (messageType == ix::WebSocketMessageType::Error)
|
||||
{
|
||||
ss << "cmd_websocket_chat: Error ! " << error.reason;
|
||||
log(ss.str());
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Ping)
|
||||
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||
{
|
||||
log("cmd_websocket_chat: received ping message");
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Pong)
|
||||
else if (messageType == ix::WebSocketMessageType::Pong)
|
||||
{
|
||||
log("cmd_websocket_chat: received pong message");
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Fragment)
|
||||
else if (messageType == ix::WebSocketMessageType::Fragment)
|
||||
{
|
||||
log("cmd_websocket_chat: received message fragment");
|
||||
}
|
||||
@ -228,7 +228,7 @@ namespace
|
||||
const ix::WebSocketOpenInfo& openInfo,
|
||||
const ix::WebSocketCloseInfo& closeInfo)
|
||||
{
|
||||
if (messageType == ix::WebSocket_MessageType_Open)
|
||||
if (messageType == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
Logger() << "New connection";
|
||||
Logger() << "id: " << connectionState->getId();
|
||||
@ -239,11 +239,11 @@ namespace
|
||||
Logger() << it.first << ": " << it.second;
|
||||
}
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||
else if (messageType == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
log("Closed connection");
|
||||
}
|
||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
||||
else if (messageType == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
for (auto&& client : server.getClients())
|
||||
{
|
||||
|
105
test/run.py
105
test/run.py
@ -28,9 +28,9 @@ try:
|
||||
except ImportError:
|
||||
hasClick = False
|
||||
|
||||
|
||||
DEFAULT_EXE = 'ixwebsocket_unittest'
|
||||
|
||||
BUILD_TYPE = 'Debug'
|
||||
XML_OUTPUT_FILE = 'ixwebsocket_unittest.xml'
|
||||
TEST_EXE_PATH = None
|
||||
|
||||
class Command(object):
|
||||
"""Run system commands with timeout
|
||||
@ -65,7 +65,7 @@ class Command(object):
|
||||
return True, self.process.returncode
|
||||
|
||||
|
||||
def runCommand(cmd, assertOnFailure=True, timeout=None):
|
||||
def runCommand(cmd, abortOnFailure=True, timeout=None):
|
||||
'''Small wrapper to run a command and make sure it succeed'''
|
||||
|
||||
if timeout is None:
|
||||
@ -73,16 +73,13 @@ def runCommand(cmd, assertOnFailure=True, timeout=None):
|
||||
|
||||
print('\nRunning', cmd)
|
||||
command = Command(cmd)
|
||||
timedout, ret = command.run(timeout)
|
||||
succeed, ret = command.run(timeout)
|
||||
|
||||
if timedout:
|
||||
print('Unittest timed out')
|
||||
|
||||
msg = 'cmd {} failed with error code {}'.format(cmd, ret)
|
||||
if ret != 0:
|
||||
if not succeed or ret != 0:
|
||||
msg = 'cmd {}\nfailed with error code {}'.format(cmd, ret)
|
||||
print(msg)
|
||||
if assertOnFailure:
|
||||
assert False
|
||||
if abortOnFailure:
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
def runCMake(sanitizer, buildDir):
|
||||
@ -91,12 +88,6 @@ def runCMake(sanitizer, buildDir):
|
||||
(remove build sub-folder).
|
||||
'''
|
||||
|
||||
# CMake installed via Self Service ends up here.
|
||||
cmake_executable = '/Applications/CMake.app/Contents/bin/cmake'
|
||||
|
||||
if not os.path.exists(cmake_executable):
|
||||
cmake_executable = 'cmake'
|
||||
|
||||
sanitizersFlags = {
|
||||
'asan': '-DSANITIZE_ADDRESS=On',
|
||||
'ubsan': '-DSANITIZE_UNDEFINED=On',
|
||||
@ -110,19 +101,22 @@ def runCMake(sanitizer, buildDir):
|
||||
if not os.path.exists(cmakeExecutable):
|
||||
cmakeExecutable = 'cmake'
|
||||
|
||||
generator = '"Unix Makefiles"'
|
||||
if platform.system() == 'Windows':
|
||||
generator = '"NMake Makefiles"'
|
||||
#generator = '"NMake Makefiles"'
|
||||
generator = '"Visual Studio 16 2019"'
|
||||
else:
|
||||
generator = '"Unix Makefiles"'
|
||||
|
||||
fmt = '''
|
||||
{cmakeExecutable} -H. \
|
||||
CMAKE_BUILD_TYPE = BUILD_TYPE
|
||||
|
||||
fmt = '{cmakeExecutable} -H. \
|
||||
{sanitizerFlag} \
|
||||
-B{buildDir} \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-B"{buildDir}" \
|
||||
-DCMAKE_BUILD_TYPE={CMAKE_BUILD_TYPE} \
|
||||
-DUSE_TLS=1 \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-G{generator}
|
||||
'''
|
||||
-G{generator}'
|
||||
|
||||
cmakeCmd = fmt.format(**locals())
|
||||
runCommand(cmakeCmd)
|
||||
|
||||
@ -133,10 +127,10 @@ def runTest(args, buildDir, xmlOutput, testRunName):
|
||||
if args is None:
|
||||
args = ''
|
||||
|
||||
fmt = '{buildDir}/{DEFAULT_EXE} -o {xmlOutput} -n "{testRunName}" -r junit "{args}"'
|
||||
testCommand = fmt.format(**locals())
|
||||
testCommand = '{} -o {} -n "{}" -r junit "{}"'.format(TEST_EXE_PATH, xmlOutput, testRunName, args)
|
||||
|
||||
runCommand(testCommand,
|
||||
assertOnFailure=False)
|
||||
abortOnFailure=False)
|
||||
|
||||
|
||||
def validateTestSuite(xmlOutput):
|
||||
@ -296,8 +290,7 @@ def executeJobs(jobs):
|
||||
def computeAllTestNames(buildDir):
|
||||
'''Compute all test case names, by executing the unittest in a custom mode'''
|
||||
|
||||
executable = os.path.join(buildDir, DEFAULT_EXE)
|
||||
cmd = '"{}" --list-test-names-only'.format(executable)
|
||||
cmd = '"{}" --list-test-names-only'.format(TEST_EXE_PATH)
|
||||
names = os.popen(cmd).read().splitlines()
|
||||
names.sort() # Sort test names for execution determinism
|
||||
return names
|
||||
@ -344,7 +337,7 @@ def generateXmlOutput(results, xmlOutput, testRunName, runTime):
|
||||
})
|
||||
|
||||
systemOut = ET.Element('system-out')
|
||||
systemOut.text = result['output'].decode('utf-8')
|
||||
systemOut.text = result['output'].decode('utf-8', 'ignore')
|
||||
testCase.append(systemOut)
|
||||
|
||||
if not result['success']:
|
||||
@ -365,16 +358,19 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD
|
||||
runCMake(sanitizer, buildDir)
|
||||
|
||||
# build with make
|
||||
makeCmd = 'make'
|
||||
jobs = '-j8'
|
||||
#makeCmd = 'cmake --build '
|
||||
#jobs = '-j8'
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
makeCmd = 'nmake'
|
||||
#if platform.system() == 'Windows':
|
||||
# makeCmd = 'nmake'
|
||||
|
||||
# nmake does not have a -j option
|
||||
jobs = ''
|
||||
# jobs = ''
|
||||
|
||||
runCommand('{} -C {} {}'.format(makeCmd, buildDir, jobs))
|
||||
#runCommand('{} -C {} {}'.format(makeCmd, buildDir, jobs))
|
||||
|
||||
# build with cmake
|
||||
runCommand('cmake --build ' + buildDir)
|
||||
|
||||
if buildOnly:
|
||||
return
|
||||
@ -409,12 +405,7 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD
|
||||
continue
|
||||
|
||||
# testName can contains spaces, so we enclose them in double quotes
|
||||
executable = os.path.join(buildDir, DEFAULT_EXE)
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
executable += '.exe'
|
||||
|
||||
cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, executable, testName, outputPath)
|
||||
cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, TEST_EXE_PATH, testName, outputPath)
|
||||
|
||||
jobs.append({
|
||||
'name': testName,
|
||||
@ -454,8 +445,6 @@ def main():
|
||||
if not os.path.exists(buildDir):
|
||||
os.makedirs(buildDir)
|
||||
|
||||
defaultOutput = DEFAULT_EXE + '.xml'
|
||||
|
||||
parser = argparse.ArgumentParser(description='Build and Run the engine unittest')
|
||||
|
||||
sanitizers = ['tsan', 'asan', 'ubsan', 'none']
|
||||
@ -481,14 +470,29 @@ def main():
|
||||
|
||||
# Default sanitizer is tsan
|
||||
sanitizer = args.sanitizer
|
||||
if args.sanitizer is None:
|
||||
|
||||
if args.no_sanitizer:
|
||||
sanitizer = 'none'
|
||||
elif args.sanitizer is None:
|
||||
sanitizer = 'tsan'
|
||||
|
||||
# Sanitizers display lots of strange errors on Linux on CI,
|
||||
# which looks like false positives
|
||||
if platform.system() != 'Darwin':
|
||||
sanitizer = 'none'
|
||||
|
||||
defaultRunName = 'ixengine_{}_{}'.format(platform.system(), sanitizer)
|
||||
|
||||
xmlOutput = args.output or defaultOutput
|
||||
xmlOutput = args.output or XML_OUTPUT_FILE
|
||||
testRunName = args.run_name or os.getenv('IXENGINE_TEST_RUN_NAME') or defaultRunName
|
||||
|
||||
global TEST_EXE_PATH
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
TEST_EXE_PATH = os.path.join(buildDir, BUILD_TYPE, 'ixwebsocket_unittest.exe')
|
||||
else:
|
||||
TEST_EXE_PATH = os.path.join(buildDir, 'ixwebsocket_unittest')
|
||||
|
||||
if args.list:
|
||||
# catch2 exit with a different error code when requesting the list of files
|
||||
try:
|
||||
@ -505,11 +509,6 @@ def main():
|
||||
print('LLDB is only supported on Apple at this point')
|
||||
args.lldb = False
|
||||
|
||||
# Sanitizers display lots of strange errors on Linux on CI,
|
||||
# which looks like false positives
|
||||
if platform.system() != 'Darwin':
|
||||
sanitizer = 'none'
|
||||
|
||||
return run(args.test, buildDir, sanitizer, xmlOutput,
|
||||
testRunName, args.build_only, args.lldb)
|
||||
|
||||
|
@ -7,12 +7,21 @@
|
||||
#define CATCH_CONFIG_RUNNER
|
||||
#include "catch.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
ix::initNetSystem();
|
||||
|
||||
ix::IXCoreLogger::LogFunc logFunc = [](const char* msg)
|
||||
{
|
||||
spdlog::info(msg);
|
||||
};
|
||||
ix::IXCoreLogger::setLogFunction(logFunc);
|
||||
|
||||
int result = Catch::Session().run(argc, argv);
|
||||
|
||||
ix::uninitNetSystem();
|
||||
|
4
third_party/README.md
vendored
4
third_party/README.md
vendored
@ -1 +1,3 @@
|
||||
Except ZLIB on Windows (whose port is currently broken...) all dependencies here are for the ws command line tools, not for the IXWebSockets library which is standalone.
|
||||
# Note
|
||||
|
||||
Except *zlib* on Windows, all dependencies here are for the ws command line tools, not for the IXWebSockets library which is standalone.
|
||||
|
58
third_party/msgpack11/msgpack11.cpp
vendored
58
third_party/msgpack11/msgpack11.cpp
vendored
@ -119,62 +119,62 @@ static void verify_length(size_t len)
|
||||
}
|
||||
|
||||
static void dump(NullStruct, std::string &out) {
|
||||
out.push_back(0xc0);
|
||||
out.push_back((char) 0xc0);
|
||||
}
|
||||
|
||||
static void dump(float value, std::string &out) {
|
||||
out.push_back(0xca);
|
||||
out.push_back((char) 0xca);
|
||||
dump_data(value, out);
|
||||
}
|
||||
|
||||
static void dump(double value, std::string &out) {
|
||||
out.push_back(0xcb);
|
||||
out.push_back((char) 0xcb);
|
||||
dump_data(value, out);
|
||||
}
|
||||
|
||||
static void dump(int8_t value, std::string &out) {
|
||||
if( value < -32 )
|
||||
{
|
||||
out.push_back(0xd0);
|
||||
out.push_back((char) 0xd0);
|
||||
}
|
||||
out.push_back(value);
|
||||
}
|
||||
|
||||
static void dump(int16_t value, std::string &out) {
|
||||
out.push_back(0xd1);
|
||||
out.push_back((char) 0xd1);
|
||||
dump_data(value, out);
|
||||
}
|
||||
|
||||
static void dump(int32_t value, std::string &out) {
|
||||
out.push_back(0xd2);
|
||||
out.push_back((char) 0xd2);
|
||||
dump_data(value, out);
|
||||
}
|
||||
|
||||
static void dump(int64_t value, std::string &out) {
|
||||
out.push_back(0xd3);
|
||||
out.push_back((char) 0xd3);
|
||||
dump_data(value, out);
|
||||
}
|
||||
|
||||
static void dump(uint8_t value, std::string &out) {
|
||||
if(128 <= value)
|
||||
{
|
||||
out.push_back(0xcc);
|
||||
out.push_back((char) 0xcc);
|
||||
}
|
||||
out.push_back(value);
|
||||
}
|
||||
|
||||
static void dump(uint16_t value, std::string &out) {
|
||||
out.push_back(0xcd);
|
||||
out.push_back((char) 0xcd);
|
||||
dump_data(value, out);
|
||||
}
|
||||
|
||||
static void dump(uint32_t value, std::string &out) {
|
||||
out.push_back(0xce);
|
||||
out.push_back((char) 0xce);
|
||||
dump_data(value, out);
|
||||
}
|
||||
|
||||
static void dump(uint64_t value, std::string &out) {
|
||||
out.push_back(0xcf);
|
||||
out.push_back((char) 0xcf);
|
||||
dump_data(value, out);
|
||||
}
|
||||
|
||||
@ -194,19 +194,19 @@ static void dump(const std::string& value, std::string &out) {
|
||||
else if(len <= 0xff)
|
||||
{
|
||||
uint8_t const length = static_cast<uint8_t>(len);
|
||||
out.push_back(0xd9);
|
||||
out.push_back((char) 0xd9);
|
||||
out.push_back(length);
|
||||
}
|
||||
else if(len <= 0xffff)
|
||||
{
|
||||
uint16_t const length = static_cast<uint16_t>(len);
|
||||
out.push_back(0xda);
|
||||
out.push_back((char) 0xda);
|
||||
dump_data(length, out);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t const length = static_cast<uint32_t>(len);
|
||||
out.push_back(0xdb);
|
||||
out.push_back((char) 0xdb);
|
||||
dump_data(length, out);
|
||||
}
|
||||
|
||||
@ -226,13 +226,13 @@ static void dump(const MsgPack::array& value, std::string &out) {
|
||||
else if(len <= 0xffff)
|
||||
{
|
||||
uint16_t const length = static_cast<uint16_t>(len);
|
||||
out.push_back(0xdc);
|
||||
out.push_back((char) 0xdc);
|
||||
dump_data(length, out);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t const length = static_cast<uint32_t>(len);
|
||||
out.push_back(0xdd);
|
||||
out.push_back((char) 0xdd);
|
||||
dump_data(length, out);
|
||||
}
|
||||
|
||||
@ -252,13 +252,13 @@ static void dump(const MsgPack::object& value, std::string &out) {
|
||||
else if(len <= 0xffff)
|
||||
{
|
||||
uint16_t const length = static_cast<uint16_t>(len);
|
||||
out.push_back(0xde);
|
||||
out.push_back((char) 0xde);
|
||||
dump_data(length, out);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t const length = static_cast<uint32_t>(len);
|
||||
out.push_back(0xdf);
|
||||
out.push_back((char) 0xdf);
|
||||
dump_data(length, out);
|
||||
}
|
||||
|
||||
@ -274,19 +274,19 @@ static void dump(const MsgPack::binary& value, std::string &out) {
|
||||
if(len <= 0xff)
|
||||
{
|
||||
uint8_t const length = static_cast<uint8_t>(len);
|
||||
out.push_back(0xc4);
|
||||
out.push_back((char) 0xc4);
|
||||
dump_data(length, out);
|
||||
}
|
||||
else if(len <= 0xffff)
|
||||
{
|
||||
uint16_t const length = static_cast<uint16_t>(len);
|
||||
out.push_back(0xc5);
|
||||
out.push_back((char) 0xc5);
|
||||
dump_data(length, out);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t const length = static_cast<uint32_t>(len);
|
||||
out.push_back(0xc6);
|
||||
out.push_back((char) 0xc6);
|
||||
dump_data(length, out);
|
||||
}
|
||||
|
||||
@ -302,33 +302,33 @@ static void dump(const MsgPack::extension& value, std::string &out) {
|
||||
verify_length(len);
|
||||
|
||||
if(len == 0x01) {
|
||||
out.push_back(0xd4);
|
||||
out.push_back((char) 0xd4);
|
||||
}
|
||||
else if(len == 0x02) {
|
||||
out.push_back(0xd5);
|
||||
out.push_back((char) 0xd5);
|
||||
}
|
||||
else if(len == 0x04) {
|
||||
out.push_back(0xd6);
|
||||
out.push_back((char) 0xd6);
|
||||
}
|
||||
else if(len == 0x08) {
|
||||
out.push_back(0xd7);
|
||||
out.push_back((char) 0xd7);
|
||||
}
|
||||
else if(len == 0x10) {
|
||||
out.push_back(0xd8);
|
||||
out.push_back((char) 0xd8);
|
||||
}
|
||||
else if(len <= 0xff) {
|
||||
uint8_t const length = static_cast<uint8_t>(len);
|
||||
out.push_back(0xc7);
|
||||
out.push_back((char) 0xc7);
|
||||
out.push_back(length);
|
||||
}
|
||||
else if(len <= 0xffff) {
|
||||
uint16_t const length = static_cast<uint16_t>(len);
|
||||
out.push_back(0xc8);
|
||||
out.push_back((char) 0xc8);
|
||||
dump_data(length, out);
|
||||
}
|
||||
else {
|
||||
uint32_t const length = static_cast<uint32_t>(len);
|
||||
out.push_back(0xc9);
|
||||
out.push_back((char) 0xc9);
|
||||
dump_data(length, out);
|
||||
}
|
||||
|
||||
|
108
third_party/spdlog/.clang-format
vendored
Normal file
108
third_party/spdlog/.clang-format
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: true
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: true
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 140
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeCategories:
|
||||
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
|
||||
Priority: 2
|
||||
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||
Priority: 3
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IndentCaseLabels: false
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Right
|
||||
ReflowComments: true
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
...
|
||||
|
28
third_party/spdlog/.clang-tidy
vendored
Normal file
28
third_party/spdlog/.clang-tidy
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
Checks: 'modernize-*,modernize-use-override,google-*,-google-runtime-references,misc-*,clang-analyzer-*'
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: 'async.h|async_logger.h|common.h|details|formatter.h|logger.h|sinks|spdlog.h|tweakme.h|version.h'
|
||||
AnalyzeTemporaryDtors: false
|
||||
FormatStyle: none
|
||||
|
||||
CheckOptions:
|
||||
- key: google-readability-braces-around-statements.ShortStatementLines
|
||||
value: '1'
|
||||
- key: google-readability-function-size.StatementThreshold
|
||||
value: '800'
|
||||
- key: google-readability-namespace-comments.ShortNamespaceLines
|
||||
value: '10'
|
||||
- key: google-readability-namespace-comments.SpacesBeforeComments
|
||||
value: '2'
|
||||
- key: modernize-loop-convert.MaxCopySize
|
||||
value: '16'
|
||||
- key: modernize-loop-convert.MinConfidence
|
||||
value: reasonable
|
||||
- key: modernize-loop-convert.NamingStyle
|
||||
value: CamelCase
|
||||
- key: modernize-pass-by-value.IncludeStyle
|
||||
value: llvm
|
||||
- key: modernize-replace-auto-ptr.IncludeStyle
|
||||
value: llvm
|
||||
- key: modernize-use-nullptr.NullMacros
|
||||
value: 'NULL'
|
||||
|
68
third_party/spdlog/.gitignore
vendored
Normal file
68
third_party/spdlog/.gitignore
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
# Auto generated files
|
||||
build/*
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
*.suo
|
||||
*.tlog
|
||||
*.ilk
|
||||
*.log
|
||||
*.pdb
|
||||
*.idb
|
||||
*.iobj
|
||||
*.ipdb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# Codelite
|
||||
.codelite
|
||||
|
||||
# .orig files
|
||||
*.orig
|
||||
|
||||
# example files
|
||||
example/*
|
||||
!example/example.cpp
|
||||
!example/bench.cpp
|
||||
!example/utils.h
|
||||
!example/Makefile*
|
||||
!example/example.sln
|
||||
!example/example.vcxproj
|
||||
!example/CMakeLists.txt
|
||||
!example/multisink.cpp
|
||||
!example/jni
|
||||
|
||||
# generated files
|
||||
generated
|
||||
|
||||
# Cmake
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
CMakeScripts
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
/tests/tests.VC.VC.opendb
|
||||
/tests/tests.VC.db
|
||||
/tests/tests
|
||||
/tests/logs/*
|
||||
|
||||
# idea
|
||||
.idea/
|
116
third_party/spdlog/.travis.yml
vendored
Normal file
116
third_party/spdlog/.travis.yml
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
# Adapted from various sources, including:
|
||||
# - Louis Dionne's Hana: https://github.com/ldionne/hana
|
||||
# - Paul Fultz II's FIT: https://github.com/pfultz2/Fit
|
||||
# - Eric Niebler's range-v3: https://github.com/ericniebler/range-v3
|
||||
sudo: required
|
||||
language: cpp
|
||||
|
||||
addons: &gcc48
|
||||
apt:
|
||||
packages:
|
||||
- g++-4.8
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
|
||||
addons: &gcc7
|
||||
apt:
|
||||
packages:
|
||||
- g++-7
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
|
||||
addons: &clang35
|
||||
apt:
|
||||
packages:
|
||||
- clang-3.5
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-precise-3.5
|
||||
|
||||
addons: &clang6
|
||||
apt:
|
||||
packages:
|
||||
- clang-6.0
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-trusty-6.0
|
||||
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Test gcc-4.8: C++11, Build=Debug/Release
|
||||
- env: GCC_VERSION=4.8 BUILD_TYPE=Debug CPP=11
|
||||
os: linux
|
||||
addons: *gcc48
|
||||
|
||||
- env: GCC_VERSION=4.8 BUILD_TYPE=Release CPP=11
|
||||
os: linux
|
||||
addons: *gcc48
|
||||
|
||||
- env: GCC_VERSION=7 BUILD_TYPE=Release CPP=11
|
||||
os: linux
|
||||
addons: *gcc7
|
||||
|
||||
# Test clang-3.5: C++11, Build=Debug/Release
|
||||
- env: CLANG_VERSION=3.5 BUILD_TYPE=Debug CPP=11
|
||||
os: linux
|
||||
addons: *clang35
|
||||
|
||||
- env: CLANG_VERSION=3.5 BUILD_TYPE=Release CPP=11
|
||||
os: linux
|
||||
addons: *clang35
|
||||
|
||||
# Test clang-6.0: C++11, Build=Debug, ASAN=On
|
||||
- env: CLANG_VERSION=6.0 BUILD_TYPE=Debug CPP=11 ASAN=On TSAN=Off
|
||||
os: linux
|
||||
addons: *clang6
|
||||
|
||||
- env: CLANG_VERSION=6.0 BUILD_TYPE=Release CPP=11 ASAN=On TSAN=Off
|
||||
os: linux
|
||||
addons: *clang6
|
||||
|
||||
# Test clang-6.0: C++11, Build=Debug, TSAN=On
|
||||
- env: CLANG_VERSION=6.0 BUILD_TYPE=Debug CPP=11 ASAN=Off TSAN=On
|
||||
os: linux
|
||||
addons: *clang6
|
||||
|
||||
- env: CLANG_VERSION=6.0 BUILD_TYPE=Release CPP=11 ASAN=Off TSAN=On
|
||||
os: linux
|
||||
addons: *clang6
|
||||
|
||||
# osx
|
||||
- env: BUILD_TYPE=Release CPP=11 ASAN=Off TSAN=Off
|
||||
os: osx
|
||||
|
||||
|
||||
|
||||
|
||||
before_script:
|
||||
- if [ -n "$GCC_VERSION" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi
|
||||
- if [ -n "$CLANG_VERSION" ]; then export CXX="clang++-${CLANG_VERSION}" CC="clang-${CLANG_VERSION}"; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export CXX="clang++" CC="clang"; fi
|
||||
- which $CXX
|
||||
- which $CC
|
||||
- $CXX --version
|
||||
- cmake --version
|
||||
|
||||
script:
|
||||
- cd ${TRAVIS_BUILD_DIR}
|
||||
- mkdir -p build && cd build
|
||||
- |
|
||||
cmake .. \
|
||||
--warn-uninitialized \
|
||||
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
|
||||
-DCMAKE_CXX_STANDARD=$CPP \
|
||||
-DSPDLOG_BUILD_EXAMPLES=ON \
|
||||
-DSPDLOG_BUILD_BENCH=OFF \
|
||||
-DSPDLOG_BUILD_TESTS=ON \
|
||||
-DSPDLOG_SANITIZE_ADDRESS=$ASAN \
|
||||
-DSPDLOG_SANITIZE_THREAD=$TSAN
|
||||
- make VERBOSE=1 -j2
|
||||
- ctest -j2 --output-on-failure
|
||||
|
||||
|
||||
|
||||
notifications:
|
||||
email: false
|
157
third_party/spdlog/CMakeLists.txt
vendored
Normal file
157
third_party/spdlog/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
#
|
||||
# Copyright(c) 2015 Ruslan Baratov.
|
||||
# Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
project(spdlog VERSION 1.3.1 LANGUAGES CXX)
|
||||
include(CMakeDependentOption)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
# set default build to release
|
||||
#---------------------------------------------------------------------------------------
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE)
|
||||
endif()
|
||||
|
||||
message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
# compiler config
|
||||
#---------------------------------------------------------------------------------------
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
||||
add_compile_options("-Wall")
|
||||
add_compile_options("-Wextra")
|
||||
add_compile_options("-Wconversion")
|
||||
add_compile_options("-pedantic")
|
||||
add_compile_options("-Wfatal-errors")
|
||||
|
||||
endif()
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
# address sanitizers check
|
||||
#---------------------------------------------------------------------------------------
|
||||
include(cmake/sanitizers.cmake)
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
# spdlog target
|
||||
#---------------------------------------------------------------------------------------
|
||||
add_library(spdlog INTERFACE)
|
||||
add_library(spdlog::spdlog ALIAS spdlog)
|
||||
|
||||
# Check if spdlog is being used directly or via add_subdirectory
|
||||
set(SPDLOG_MASTER_PROJECT OFF)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(SPDLOG_MASTER_PROJECT ON)
|
||||
endif()
|
||||
|
||||
option(SPDLOG_BUILD_EXAMPLES "Build examples" ${SPDLOG_MASTER_PROJECT})
|
||||
option(SPDLOG_BUILD_BENCH "Build benchmarks" ${SPDLOG_MASTER_PROJECT})
|
||||
option(SPDLOG_BUILD_TESTS "Build tests" ${SPDLOG_MASTER_PROJECT})
|
||||
option(SPDLOG_FMT_EXTERNAL "Use external fmt library instead of bundled" OFF)
|
||||
|
||||
if(SPDLOG_FMT_EXTERNAL)
|
||||
find_package(fmt REQUIRED CONFIG)
|
||||
endif()
|
||||
|
||||
target_include_directories(
|
||||
spdlog
|
||||
INTERFACE
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
|
||||
)
|
||||
|
||||
if(SPDLOG_FMT_EXTERNAL)
|
||||
target_compile_definitions(spdlog INTERFACE SPDLOG_FMT_EXTERNAL)
|
||||
target_link_libraries(spdlog INTERFACE fmt::fmt)
|
||||
endif()
|
||||
|
||||
set(HEADER_BASE "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
|
||||
if(SPDLOG_BUILD_EXAMPLES)
|
||||
add_subdirectory(example)
|
||||
endif()
|
||||
|
||||
if(SPDLOG_BUILD_TESTS)
|
||||
include(CTest)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
if(SPDLOG_BUILD_BENCH)
|
||||
add_subdirectory(bench)
|
||||
endif()
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
# Install/export targets and files
|
||||
#---------------------------------------------------------------------------------------
|
||||
# set files and directories
|
||||
set(config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
|
||||
set(include_install_dir "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
set(pkgconfig_install_dir "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
|
||||
set(version_config "${CMAKE_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake")
|
||||
set(project_config "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake")
|
||||
set(targets_config "${PROJECT_NAME}Targets.cmake")
|
||||
set(pkg_config "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc")
|
||||
set(targets_export_name "${PROJECT_NAME}Targets")
|
||||
set(namespace "${PROJECT_NAME}::")
|
||||
|
||||
# generate package version file
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file(
|
||||
"${version_config}" COMPATIBILITY SameMajorVersion
|
||||
)
|
||||
|
||||
# configure pkg config file
|
||||
configure_file("cmake/spdlog.pc.in" "${pkg_config}" @ONLY)
|
||||
# configure spdlogConfig.cmake file
|
||||
configure_file("cmake/Config.cmake.in" "${project_config}" @ONLY)
|
||||
|
||||
# install targets
|
||||
install(
|
||||
TARGETS spdlog
|
||||
EXPORT "${targets_export_name}"
|
||||
)
|
||||
|
||||
# install headers
|
||||
install(
|
||||
DIRECTORY "${HEADER_BASE}/${PROJECT_NAME}"
|
||||
DESTINATION "${include_install_dir}"
|
||||
)
|
||||
|
||||
# install project config and version file
|
||||
install(
|
||||
FILES "${project_config}" "${version_config}"
|
||||
DESTINATION "${config_install_dir}"
|
||||
)
|
||||
|
||||
# install pkg config file
|
||||
install(
|
||||
FILES "${pkg_config}"
|
||||
DESTINATION "${pkgconfig_install_dir}"
|
||||
)
|
||||
|
||||
# install targets config file
|
||||
install(
|
||||
EXPORT "${targets_export_name}"
|
||||
NAMESPACE "${namespace}"
|
||||
DESTINATION "${config_install_dir}"
|
||||
FILE ${targets_config}
|
||||
)
|
||||
|
||||
# export build directory targets file
|
||||
export(
|
||||
EXPORT ${targets_export_name}
|
||||
NAMESPACE "${namespace}"
|
||||
FILE ${targets_config}
|
||||
)
|
||||
|
||||
# register project in CMake user registry
|
||||
export(PACKAGE ${PROJECT_NAME})
|
||||
|
||||
file(GLOB_RECURSE spdlog_include_SRCS "${HEADER_BASE}/*.h")
|
||||
add_custom_target(spdlog_headers_for_ide SOURCES ${spdlog_include_SRCS})
|
13
third_party/spdlog/INSTALL
vendored
Normal file
13
third_party/spdlog/INSTALL
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
spdlog is header only library.
|
||||
Just copy the files to your build tree and use a C++11 compiler
|
||||
|
||||
Tested on:
|
||||
gcc 4.8.1 and above
|
||||
clang 3.5
|
||||
Visual Studio 2013
|
||||
|
||||
gcc 4.8 flags: --std==c++11 -pthread -O3 -flto -Wl,--no-as-needed
|
||||
gcc 4.9 flags: --std=c++11 -pthread -O3 -flto
|
||||
|
||||
|
||||
see the makefile in the example folder
|
22
third_party/spdlog/LICENSE
vendored
Normal file
22
third_party/spdlog/LICENSE
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Gabi Melman.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
319
third_party/spdlog/README.md
vendored
Normal file
319
third_party/spdlog/README.md
vendored
Normal file
@ -0,0 +1,319 @@
|
||||
# spdlog
|
||||
|
||||
Very fast, header only, C++ logging library. [](https://travis-ci.org/gabime/spdlog) [](https://ci.appveyor.com/project/gabime/spdlog)
|
||||
|
||||
|
||||
|
||||
## Install
|
||||
#### Just copy the headers:
|
||||
|
||||
* Copy the source [folder](https://github.com/gabime/spdlog/tree/v1.x/include/spdlog) to your build tree and use a C++11 compiler.
|
||||
|
||||
#### Or use your favorite package manager:
|
||||
|
||||
* Ubuntu: `apt-get install libspdlog-dev`
|
||||
* Homebrew: `brew install spdlog`
|
||||
* FreeBSD: `cd /usr/ports/devel/spdlog/ && make install clean`
|
||||
* Fedora: `yum install spdlog`
|
||||
* Gentoo: `emerge dev-libs/spdlog`
|
||||
* Arch Linux: `yaourt -S spdlog-git`
|
||||
* vcpkg: `vcpkg install spdlog`
|
||||
|
||||
|
||||
## Platforms
|
||||
* Linux, FreeBSD, OpenBSD, Solaris, AIX
|
||||
* Windows (msvc 2013+, cygwin)
|
||||
* macOS (clang 3.5+)
|
||||
* Android
|
||||
|
||||
## Features
|
||||
* Very fast (see [benchmarks](#benchmarks) below).
|
||||
* Headers only, just copy and use.
|
||||
* Feature rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library.
|
||||
* Fast asynchronous mode (optional)
|
||||
* [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting.
|
||||
* Multi/Single threaded loggers.
|
||||
* Various log targets:
|
||||
* Rotating log files.
|
||||
* Daily log files.
|
||||
* Console logging (colors supported).
|
||||
* syslog.
|
||||
* Windows debugger (```OutputDebugString(..)```)
|
||||
* Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface).
|
||||
* Severity based filtering - threshold levels can be modified in runtime as well as in compile time.
|
||||
* Binary data logging.
|
||||
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v1.x/bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz
|
||||
|
||||
#### Synchronous mode
|
||||
```
|
||||
*******************************************************************************
|
||||
Single thread, 1,000,000 iterations
|
||||
*******************************************************************************
|
||||
basic_st... Elapsed: 0.181652 5,505,042/sec
|
||||
rotating_st... Elapsed: 0.181781 5,501,117/sec
|
||||
daily_st... Elapsed: 0.187595 5,330,630/sec
|
||||
null_st... Elapsed: 0.0504704 19,813,602/sec
|
||||
*******************************************************************************
|
||||
10 threads sharing same logger, 1,000,000 iterations
|
||||
*******************************************************************************
|
||||
basic_mt... Elapsed: 0.616035 1,623,284/sec
|
||||
rotating_mt... Elapsed: 0.620344 1,612,008/sec
|
||||
daily_mt... Elapsed: 0.648353 1,542,369/sec
|
||||
null_mt... Elapsed: 0.151972 6,580,166/sec
|
||||
```
|
||||
#### Asynchronous mode
|
||||
```
|
||||
*******************************************************************************
|
||||
10 threads sharing same logger, 1,000,000 iterations
|
||||
*******************************************************************************
|
||||
async... Elapsed: 0.350066 2,856,606/sec
|
||||
async... Elapsed: 0.314865 3,175,960/sec
|
||||
async... Elapsed: 0.349851 2,858,358/sec
|
||||
```
|
||||
|
||||
## Usage samples
|
||||
|
||||
#### Basic usage
|
||||
```c++
|
||||
#include "spdlog/spdlog.h"
|
||||
int main()
|
||||
{
|
||||
spdlog::info("Welcome to spdlog!");
|
||||
spdlog::error("Some error message with arg: {}", 1);
|
||||
|
||||
spdlog::warn("Easy padding in numbers like {:08d}", 12);
|
||||
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
|
||||
spdlog::info("Support for floats {:03.2f}", 1.23456);
|
||||
spdlog::info("Positional args are {1} {0}..", "too", "supported");
|
||||
spdlog::info("{:<30}", "left aligned");
|
||||
|
||||
spdlog::set_level(spdlog::level::debug); // Set global log level to debug
|
||||
spdlog::debug("This message should be displayed..");
|
||||
|
||||
// change log pattern
|
||||
spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
|
||||
|
||||
// Compile time log levels
|
||||
// define SPDLOG_ACTIVE_LEVEL to desired level
|
||||
SPDLOG_TRACE("Some trace message with param {}", {});
|
||||
SPDLOG_DEBUG("Some debug message");
|
||||
|
||||
}
|
||||
```
|
||||
#### create stdout/stderr logger object
|
||||
```c++
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
void stdout_example()
|
||||
{
|
||||
// create color multi threaded logger
|
||||
auto console = spdlog::stdout_color_mt("console");
|
||||
auto err_logger = spdlog::stderr_color_mt("stderr");
|
||||
spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)");
|
||||
}
|
||||
```
|
||||
---
|
||||
#### Basic file logger
|
||||
```c++
|
||||
#include "spdlog/sinks/basic_file_sink.h"
|
||||
void basic_logfile_example()
|
||||
{
|
||||
try
|
||||
{
|
||||
auto my_logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt");
|
||||
}
|
||||
catch (const spdlog::spdlog_ex &ex)
|
||||
{
|
||||
std::cout << "Log init failed: " << ex.what() << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
---
|
||||
#### Rotating files
|
||||
```c++
|
||||
#include "spdlog/sinks/rotating_file_sink.h"
|
||||
void rotating_example()
|
||||
{
|
||||
// Create a file rotating logger with 5mb size max and 3 rotated files
|
||||
auto rotating_logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
#### Daily files
|
||||
```c++
|
||||
|
||||
#include "spdlog/sinks/daily_file_sink.h"
|
||||
void daily_example()
|
||||
{
|
||||
// Create a daily logger - a new file is created every day on 2:30am
|
||||
auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
#### Cloning loggers
|
||||
```c++
|
||||
// clone a logger and give it new name.
|
||||
// Useful for creating subsystem loggers from some "root" logger
|
||||
void clone_example()
|
||||
{
|
||||
auto network_logger = spdlog::get("root")->clone("network");
|
||||
network_logger->info("Logging network stuff..");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
#### Periodic flush
|
||||
```c++
|
||||
// periodically flush all *registered* loggers every 3 seconds:
|
||||
// warning: only use if all your loggers are thread safe!
|
||||
spdlog::flush_every(std::chrono::seconds(3));
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
#### Binary logging
|
||||
```c++
|
||||
// log binary data as hex.
|
||||
// many types of std::container<char> types can be used.
|
||||
// ranges are supported too.
|
||||
// format flags:
|
||||
// {:X} - print in uppercase.
|
||||
// {:s} - don't separate each byte with space.
|
||||
// {:p} - don't print the position on each line start.
|
||||
// {:n} - don't split the output to lines.
|
||||
|
||||
#include "spdlog/fmt/bin_to_hex.h"
|
||||
|
||||
void binary_example()
|
||||
{
|
||||
auto console = spdlog::get("console");
|
||||
std::array<char, 80> buf;
|
||||
console->info("Binary example: {}", spdlog::to_hex(buf));
|
||||
console->info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10));
|
||||
// more examples:
|
||||
// logger->info("uppercase: {:X}", spdlog::to_hex(buf));
|
||||
// logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf));
|
||||
// logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf));
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
#### Logger with multi sinks - each with different format and log level
|
||||
```c++
|
||||
|
||||
// create logger with 2 targets with different log levels and formats.
|
||||
// the console will show only warnings or errors, while the file will log all.
|
||||
void multi_sink_example()
|
||||
{
|
||||
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
||||
console_sink->set_level(spdlog::level::warn);
|
||||
console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v");
|
||||
|
||||
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/multisink.txt", true);
|
||||
file_sink->set_level(spdlog::level::trace);
|
||||
|
||||
spdlog::logger logger("multi_sink", {console_sink, file_sink});
|
||||
logger.set_level(spdlog::level::debug);
|
||||
logger.warn("this should appear in both console and file");
|
||||
logger.info("this message should not appear in the console, only in the file");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
#### Asynchronous logging
|
||||
```c++
|
||||
#include "spdlog/async.h"
|
||||
#include "spdlog/sinks/basic_file_sink.h"
|
||||
void async_example()
|
||||
{
|
||||
// default thread pool settings can be modified *before* creating the async logger:
|
||||
// spdlog::init_thread_pool(8192, 1); // queue with 8k items and 1 backing thread.
|
||||
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async_log.txt");
|
||||
// alternatively:
|
||||
// auto async_file = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("async_file_logger", "logs/async_log.txt");
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
#### Asynchronous logger with multi sinks
|
||||
```c++
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
#include "spdlog/sinks/rotating_file_sink.h"
|
||||
|
||||
void multi_sink_example2()
|
||||
{
|
||||
spdlog::init_thread_pool(8192, 1);
|
||||
auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt >();
|
||||
auto rotating_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>("mylog.txt", 1024*1024*10, 3);
|
||||
std::vector<spdlog::sink_ptr> sinks {stdout_sink, rotating_sink};
|
||||
auto logger = std::make_shared<spdlog::async_logger>("loggername", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block);
|
||||
spdlog::register_logger(logger);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
#### User defined types
|
||||
```c++
|
||||
// user defined types logging by implementing operator<<
|
||||
#include "spdlog/fmt/ostr.h" // must be included
|
||||
struct my_type
|
||||
{
|
||||
int i;
|
||||
template<typename OStream>
|
||||
friend OStream &operator<<(OStream &os, const my_type &c)
|
||||
{
|
||||
return os << "[my_type i=" << c.i << "]";
|
||||
}
|
||||
};
|
||||
|
||||
void user_defined_example()
|
||||
{
|
||||
spdlog::get("console")->info("user defined type: {}", my_type{14});
|
||||
}
|
||||
|
||||
```
|
||||
---
|
||||
#### Custom error handler
|
||||
```c++
|
||||
void err_handler_example()
|
||||
{
|
||||
// can be set globally or per logger(logger->set_error_handler(..))
|
||||
spdlog::set_error_handler([](const std::string &msg) { spdlog::get("console")->error("*** LOGGER ERROR ***: {}", msg); });
|
||||
spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3);
|
||||
}
|
||||
|
||||
```
|
||||
---
|
||||
#### syslog
|
||||
```c++
|
||||
#include "spdlog/sinks/syslog_sink.h"
|
||||
void syslog_example()
|
||||
{
|
||||
std::string ident = "spdlog-example";
|
||||
auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID);
|
||||
syslog_logger->warn("This is warning that will end up in syslog.");
|
||||
}
|
||||
```
|
||||
---
|
||||
#### Android example
|
||||
```c++
|
||||
#include "spdlog/sinks/android_sink.h"
|
||||
void android_example()
|
||||
{
|
||||
std::string tag = "spdlog-android";
|
||||
auto android_logger = spdlog::android_logger("android", tag);
|
||||
android_logger->critical("Use \"adb shell logcat\" to view this message.");
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki/1.-QuickStart) pages.
|
34
third_party/spdlog/appveyor.yml
vendored
Normal file
34
third_party/spdlog/appveyor.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
version: 1.0.{build}
|
||||
image: Visual Studio 2015
|
||||
environment:
|
||||
matrix:
|
||||
- GENERATOR: '"MinGW Makefiles"'
|
||||
BUILD_TYPE: Debug
|
||||
- GENERATOR: '"MinGW Makefiles"'
|
||||
BUILD_TYPE: Release
|
||||
- GENERATOR: '"Visual Studio 14 2015"'
|
||||
BUILD_TYPE: Debug
|
||||
- GENERATOR: '"Visual Studio 14 2015"'
|
||||
BUILD_TYPE: Release
|
||||
- GENERATOR: '"Visual Studio 14 2015 Win64"'
|
||||
BUILD_TYPE: Debug
|
||||
- GENERATOR: '"Visual Studio 14 2015 Win64"'
|
||||
BUILD_TYPE: Release
|
||||
build_script:
|
||||
- cmd: >-
|
||||
set
|
||||
|
||||
mkdir build
|
||||
|
||||
cd build
|
||||
|
||||
set PATH=%PATH:C:\Program Files\Git\usr\bin;=%
|
||||
|
||||
set PATH=C:\mingw-w64\i686-5.3.0-posix-dwarf-rt_v4-rev0\mingw32\bin;%PATH%
|
||||
|
||||
cmake .. -G %GENERATOR% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DSPDLOG_BUILD_BENCH=OFF
|
||||
|
||||
cmake --build . --config %BUILD_TYPE%
|
||||
|
||||
test_script:
|
||||
- ctest -VV -C "%BUILD_TYPE%"
|
48
third_party/spdlog/bench/CMakeLists.txt
vendored
Normal file
48
third_party/spdlog/bench/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
# *************************************************************************/
|
||||
# * Copyright (c) 2015 Ruslan Baratov. */
|
||||
# * */
|
||||
# * Permission is hereby granted, free of charge, to any person obtaining */
|
||||
# * a copy of this software and associated documentation files (the */
|
||||
# * "Software"), to deal in the Software without restriction, including */
|
||||
# * without limitation the rights to use, copy, modify, merge, publish, */
|
||||
# * distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
# * permit persons to whom the Software is furnished to do so, subject to */
|
||||
# * the following conditions: */
|
||||
# * */
|
||||
# * The above copyright notice and this permission notice shall be */
|
||||
# * included in all copies or substantial portions of the Software. */
|
||||
# * */
|
||||
# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
# * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
# * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
# * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
# * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
# * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
# * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
# *************************************************************************/
|
||||
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
project(SpdlogBench CXX)
|
||||
|
||||
if(NOT TARGET spdlog)
|
||||
# Stand-alone build
|
||||
find_package(spdlog CONFIG REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(benchmark CONFIG REQUIRED)
|
||||
|
||||
add_executable(bench bench.cpp)
|
||||
target_link_libraries(bench PRIVATE spdlog::spdlog Threads::Threads)
|
||||
|
||||
add_executable(async_bench async_bench.cpp)
|
||||
target_link_libraries(async_bench PRIVATE spdlog::spdlog Threads::Threads)
|
||||
|
||||
add_executable(latency latency.cpp)
|
||||
target_link_libraries(latency PRIVATE benchmark::benchmark spdlog::spdlog Threads::Threads)
|
||||
|
||||
|
||||
add_executable(formatter-bench formatter-bench.cpp)
|
||||
target_link_libraries(formatter-bench PRIVATE benchmark::benchmark spdlog::spdlog Threads::Threads)
|
||||
|
||||
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/logs")
|
141
third_party/spdlog/bench/async_bench.cpp
vendored
Normal file
141
third_party/spdlog/bench/async_bench.cpp
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
//
|
||||
// Copyright(c) 2015 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
//
|
||||
// bench.cpp : spdlog benchmarks
|
||||
//
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "spdlog/async.h"
|
||||
#include "spdlog/sinks/basic_file_sink.h"
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
|
||||
#include "utils.h"
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace spdlog;
|
||||
using namespace spdlog::sinks;
|
||||
using namespace utils;
|
||||
|
||||
void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count);
|
||||
|
||||
int count_lines(const char *filename)
|
||||
{
|
||||
int counter = 0;
|
||||
auto *infile = fopen(filename, "r");
|
||||
int ch;
|
||||
while (EOF != (ch = getc(infile)))
|
||||
{
|
||||
if ('\n' == ch)
|
||||
counter++;
|
||||
}
|
||||
fclose(infile);
|
||||
|
||||
return counter;
|
||||
}
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
int howmany = 1000000;
|
||||
int queue_size = howmany + 2;
|
||||
int threads = 10;
|
||||
int iters = 3;
|
||||
|
||||
try
|
||||
{
|
||||
if (argc == 1)
|
||||
{
|
||||
spdlog::set_pattern("%v");
|
||||
spdlog::info("Usage: {} <message_count> <threads> <q_size> <iterations>", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (argc > 1)
|
||||
howmany = atoi(argv[1]);
|
||||
if (argc > 2)
|
||||
threads = atoi(argv[2]);
|
||||
if (argc > 3)
|
||||
queue_size = atoi(argv[3]);
|
||||
|
||||
if (argc > 4)
|
||||
iters = atoi(argv[4]);
|
||||
|
||||
spdlog::info("-------------------------------------------------");
|
||||
spdlog::info("Messages: {:14n}", howmany);
|
||||
spdlog::info("Threads : {:14n}", threads);
|
||||
spdlog::info("Queue : {:14n}", queue_size);
|
||||
spdlog::info("Iters : {:>14n}", iters);
|
||||
spdlog::info("-------------------------------------------------");
|
||||
|
||||
const char *filename = "logs/basic_async.log";
|
||||
|
||||
for (int i = 0; i < iters; i++)
|
||||
{
|
||||
auto tp = std::make_shared<details::thread_pool>(queue_size, 1);
|
||||
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filename, true);
|
||||
auto logger = std::make_shared<async_logger>("async_logger", std::move(file_sink), std::move(tp), async_overflow_policy::block);
|
||||
bench_mt(howmany, std::move(logger), threads);
|
||||
auto count = count_lines(filename);
|
||||
|
||||
if (count != howmany)
|
||||
{
|
||||
spdlog::error("Test failed. {} has {:n} lines instead of {:n}", filename, count, howmany);
|
||||
exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
spdlog::info("Line count OK ({:n})\n", count);
|
||||
}
|
||||
}
|
||||
spdlog::shutdown();
|
||||
}
|
||||
catch (std::exception &ex)
|
||||
{
|
||||
std::cerr << "Error: " << ex.what() << std::endl;
|
||||
perror("Last error");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void thread_fun(std::shared_ptr<spdlog::logger> logger, int howmany)
|
||||
{
|
||||
for (int i = 0; i < howmany; i++)
|
||||
{
|
||||
logger->info("Hello logger: msg number {}", i);
|
||||
}
|
||||
}
|
||||
|
||||
void bench_mt(int howmany, std::shared_ptr<spdlog::logger> logger, int thread_count)
|
||||
{
|
||||
using std::chrono::high_resolution_clock;
|
||||
vector<thread> threads;
|
||||
auto start = high_resolution_clock::now();
|
||||
|
||||
int msgs_per_thread = howmany / thread_count;
|
||||
int msgs_per_thread_mod = howmany % thread_count;
|
||||
for (int t = 0; t < thread_count; ++t)
|
||||
{
|
||||
if (t == 0 && msgs_per_thread_mod)
|
||||
threads.push_back(std::thread(thread_fun, logger, msgs_per_thread + msgs_per_thread_mod));
|
||||
else
|
||||
threads.push_back(std::thread(thread_fun, logger, msgs_per_thread));
|
||||
}
|
||||
|
||||
for (auto &t : threads)
|
||||
{
|
||||
t.join();
|
||||
};
|
||||
|
||||
auto delta = high_resolution_clock::now() - start;
|
||||
auto delta_d = duration_cast<duration<double>>(delta).count();
|
||||
spdlog::info("Elapsed: {} secs\t {:n}/sec", delta_d, int(howmany / delta_d));
|
||||
}
|
199
third_party/spdlog/bench/bench.cpp
vendored
Normal file
199
third_party/spdlog/bench/bench.cpp
vendored
Normal file
@ -0,0 +1,199 @@
|
||||
//
|
||||
// Copyright(c) 2015 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
//
|
||||
// bench.cpp : spdlog benchmarks
|
||||
//
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "spdlog/async.h"
|
||||
#include "spdlog/sinks/basic_file_sink.h"
|
||||
#include "spdlog/sinks/daily_file_sink.h"
|
||||
#include "spdlog/sinks/null_sink.h"
|
||||
#include "spdlog/sinks/rotating_file_sink.h"
|
||||
|
||||
#include "utils.h"
|
||||
#include <atomic>
|
||||
#include <cstdlib> // EXIT_FAILURE
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace spdlog;
|
||||
using namespace spdlog::sinks;
|
||||
using namespace utils;
|
||||
|
||||
void bench(int howmany, std::shared_ptr<spdlog::logger> log);
|
||||
void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count);
|
||||
void bench_default_api(int howmany, std::shared_ptr<spdlog::logger> log);
|
||||
void bench_c_string(int howmany, std::shared_ptr<spdlog::logger> log);
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
spdlog::default_logger()->set_pattern("[%^%l%$] %v");
|
||||
int howmany = 1000000;
|
||||
int queue_size = howmany + 2;
|
||||
int threads = 10;
|
||||
size_t file_size = 30 * 1024 * 1024;
|
||||
size_t rotating_files = 5;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if (argc > 1)
|
||||
howmany = atoi(argv[1]);
|
||||
if (argc > 2)
|
||||
threads = atoi(argv[2]);
|
||||
if (argc > 3)
|
||||
queue_size = atoi(argv[3]);
|
||||
|
||||
spdlog::info("**************************************************************");
|
||||
spdlog::info("Single thread, {:n} iterations", howmany);
|
||||
spdlog::info("**************************************************************");
|
||||
|
||||
auto basic_st = spdlog::basic_logger_st("basic_st", "logs/basic_st.log", true);
|
||||
bench(howmany, std::move(basic_st));
|
||||
|
||||
basic_st.reset();
|
||||
auto rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_st.log", file_size, rotating_files);
|
||||
bench(howmany, std::move(rotating_st));
|
||||
|
||||
auto daily_st = spdlog::daily_logger_st("daily_st", "logs/daily_st.log");
|
||||
bench(howmany, std::move(daily_st));
|
||||
|
||||
bench(howmany, spdlog::create<null_sink_st>("null_st"));
|
||||
|
||||
spdlog::info("**************************************************************");
|
||||
spdlog::info("C-string (400 bytes). Single thread, {:n} iterations", howmany);
|
||||
spdlog::info("**************************************************************");
|
||||
|
||||
basic_st = spdlog::basic_logger_st("basic_st", "logs/basic_cs.log", true);
|
||||
bench_c_string(howmany, std::move(basic_st));
|
||||
|
||||
rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_cs.log", file_size, rotating_files);
|
||||
bench_c_string(howmany, std::move(rotating_st));
|
||||
|
||||
daily_st = spdlog::daily_logger_st("daily_st", "logs/daily_cs.log");
|
||||
bench_c_string(howmany, std::move(daily_st));
|
||||
|
||||
bench_c_string(howmany, spdlog::create<null_sink_st>("null_st"));
|
||||
|
||||
spdlog::info("**************************************************************");
|
||||
spdlog::info("{:n} threads sharing same logger, {:n} iterations", threads, howmany);
|
||||
spdlog::info("**************************************************************");
|
||||
|
||||
auto basic_mt = spdlog::basic_logger_mt("basic_mt", "logs/basic_mt.log", true);
|
||||
bench_mt(howmany, std::move(basic_mt), threads);
|
||||
|
||||
auto rotating_mt = spdlog::rotating_logger_mt("rotating_mt", "logs/rotating_mt.log", file_size, rotating_files);
|
||||
bench_mt(howmany, std::move(rotating_mt), threads);
|
||||
|
||||
auto daily_mt = spdlog::daily_logger_mt("daily_mt", "logs/daily_mt.log");
|
||||
bench_mt(howmany, std::move(daily_mt), threads);
|
||||
bench_mt(howmany, spdlog::create<null_sink_mt>("null_mt"), threads);
|
||||
|
||||
spdlog::info("**************************************************************");
|
||||
spdlog::info("Asyncronous.. {:n} threads sharing same logger, {:n} iterations", threads, howmany);
|
||||
spdlog::info("**************************************************************");
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
spdlog::init_thread_pool(static_cast<size_t>(queue_size), 1);
|
||||
auto as = spdlog::basic_logger_mt<spdlog::async_factory>("async", "logs/basic_async.log", true);
|
||||
bench_mt(howmany, std::move(as), threads);
|
||||
}
|
||||
}
|
||||
catch (std::exception &ex)
|
||||
{
|
||||
spdlog::error(ex.what());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void bench(int howmany, std::shared_ptr<spdlog::logger> log)
|
||||
{
|
||||
using std::chrono::high_resolution_clock;
|
||||
auto start = high_resolution_clock::now();
|
||||
for (auto i = 0; i < howmany; ++i)
|
||||
{
|
||||
log->info("Hello logger: msg number {}", i);
|
||||
}
|
||||
|
||||
auto delta = high_resolution_clock::now() - start;
|
||||
auto delta_d = duration_cast<duration<double>>(delta).count();
|
||||
|
||||
spdlog::info("{:<16} Elapsed: {:0.2f} secs {:>16n}/sec", log->name(), delta_d, int(howmany / delta_d));
|
||||
spdlog::drop(log->name());
|
||||
}
|
||||
|
||||
void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count)
|
||||
{
|
||||
using std::chrono::high_resolution_clock;
|
||||
vector<thread> threads;
|
||||
auto start = high_resolution_clock::now();
|
||||
for (int t = 0; t < thread_count; ++t)
|
||||
{
|
||||
threads.push_back(std::thread([&]() {
|
||||
for (int j = 0; j < howmany / thread_count; j++)
|
||||
{
|
||||
log->info("Hello logger: msg number {}", j);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for (auto &t : threads)
|
||||
{
|
||||
t.join();
|
||||
};
|
||||
|
||||
auto delta = high_resolution_clock::now() - start;
|
||||
auto delta_d = duration_cast<duration<double>>(delta).count();
|
||||
spdlog::info("{:<16} Elapsed: {:0.2f} secs {:>16n}/sec", log->name(), delta_d, int(howmany / delta_d));
|
||||
spdlog::drop(log->name());
|
||||
}
|
||||
|
||||
void bench_default_api(int howmany, std::shared_ptr<spdlog::logger> log)
|
||||
{
|
||||
using std::chrono::high_resolution_clock;
|
||||
auto orig_default = spdlog::default_logger();
|
||||
spdlog::set_default_logger(log);
|
||||
auto start = high_resolution_clock::now();
|
||||
for (auto i = 0; i < howmany; ++i)
|
||||
{
|
||||
spdlog::info("Hello logger: msg number {}", i);
|
||||
}
|
||||
|
||||
auto delta = high_resolution_clock::now() - start;
|
||||
auto delta_d = duration_cast<duration<double>>(delta).count();
|
||||
spdlog::drop(log->name());
|
||||
spdlog::set_default_logger(std::move(orig_default));
|
||||
spdlog::info("{:<16} Elapsed: {:0.2f} secs {:>16n}/sec", log->name(), delta_d, int(howmany / delta_d));
|
||||
}
|
||||
|
||||
void bench_c_string(int howmany, std::shared_ptr<spdlog::logger> log)
|
||||
{
|
||||
const char *msg = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum pharetra metus cursus "
|
||||
"lacus placerat congue. Nulla egestas, mauris a tincidunt tempus, enim lectus volutpat mi, eu consequat sem "
|
||||
"libero nec massa. In dapibus ipsum a diam rhoncus gravida. Etiam non dapibus eros. Donec fringilla dui sed "
|
||||
"augue pretium, nec scelerisque est maximus. Nullam convallis, sem nec blandit maximus, nisi turpis ornare "
|
||||
"nisl, sit amet volutpat neque massa eu odio. Maecenas malesuada quam ex, posuere congue nibh turpis duis.";
|
||||
using std::chrono::high_resolution_clock;
|
||||
auto orig_default = spdlog::default_logger();
|
||||
spdlog::set_default_logger(log);
|
||||
auto start = high_resolution_clock::now();
|
||||
for (auto i = 0; i < howmany; ++i)
|
||||
{
|
||||
spdlog::log(level::info, msg);
|
||||
}
|
||||
|
||||
auto delta = high_resolution_clock::now() - start;
|
||||
auto delta_d = duration_cast<duration<double>>(delta).count();
|
||||
spdlog::drop(log->name());
|
||||
spdlog::set_default_logger(std::move(orig_default));
|
||||
spdlog::info("{:<16} Elapsed: {:0.2f} secs {:>16n}/sec", log->name(), delta_d, int(howmany / delta_d));
|
||||
}
|
92
third_party/spdlog/bench/formatter-bench.cpp
vendored
Normal file
92
third_party/spdlog/bench/formatter-bench.cpp
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
//
|
||||
// Copyright(c) 2018 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
#include "benchmark/benchmark.h"
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "spdlog/details/pattern_formatter.h"
|
||||
|
||||
void bench_scoped_pad(benchmark::State &state, size_t wrapped_size, spdlog::details::padding_info padinfo)
|
||||
{
|
||||
fmt::memory_buffer dest;
|
||||
for (auto _ : state)
|
||||
{
|
||||
{
|
||||
spdlog::details::scoped_pad p(wrapped_size, padinfo, dest);
|
||||
benchmark::DoNotOptimize(p);
|
||||
dest.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bench_formatter(benchmark::State &state, std::string pattern)
|
||||
{
|
||||
auto formatter = spdlog::details::make_unique<spdlog::pattern_formatter>(pattern);
|
||||
fmt::memory_buffer dest;
|
||||
std::string logger_name = "logger-name";
|
||||
const char *text = "Hello. This is some message with length of 80 ";
|
||||
|
||||
spdlog::details::log_msg msg(&logger_name, spdlog::level::info, text);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
dest.clear();
|
||||
formatter->format(msg, dest);
|
||||
benchmark::DoNotOptimize(dest);
|
||||
}
|
||||
}
|
||||
|
||||
void bench_formatters()
|
||||
{
|
||||
// basic patterns(single flag)
|
||||
std::string all_flags = "+vtPnlLaAbBcCYDmdHIMSefFprRTXzEi%";
|
||||
std::vector<std::string> basic_patterns;
|
||||
for (auto &flag : all_flags)
|
||||
{
|
||||
auto pattern = std::string("%") + flag;
|
||||
benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
|
||||
|
||||
// pattern = std::string("%16") + flag;
|
||||
// benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
|
||||
//
|
||||
// // bench center padding
|
||||
// pattern = std::string("%=16") + flag;
|
||||
// benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
|
||||
}
|
||||
|
||||
// complex patterns
|
||||
std::vector<std::string> patterns = {
|
||||
"[%D %X] [%l] [%n] %v",
|
||||
"[%Y-%m-%d %H:%M:%S.%e] [%l] [%n] %v",
|
||||
"[%Y-%m-%d %H:%M:%S.%e] [%l] [%n] [%t] %v",
|
||||
};
|
||||
for (auto &pattern : patterns)
|
||||
{
|
||||
benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern)->Iterations(2500000);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
spdlog::set_pattern("[%^%l%$] %v");
|
||||
if (argc != 2)
|
||||
{
|
||||
spdlog::error("Usage: {} <pattern> (or \"all\" to bench all)", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string pattern = argv[1];
|
||||
if (pattern == "all")
|
||||
{
|
||||
bench_formatters();
|
||||
}
|
||||
else
|
||||
{
|
||||
benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
|
||||
}
|
||||
benchmark::Initialize(&argc, argv);
|
||||
benchmark::RunSpecifiedBenchmarks();
|
||||
}
|
143
third_party/spdlog/bench/latency.cpp
vendored
Normal file
143
third_party/spdlog/bench/latency.cpp
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
//
|
||||
// Copyright(c) 2018 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
//
|
||||
// latency.cpp : spdlog latency benchmarks
|
||||
//
|
||||
|
||||
#include "benchmark/benchmark.h"
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "spdlog/async.h"
|
||||
#include "spdlog/sinks/basic_file_sink.h"
|
||||
#include "spdlog/sinks/daily_file_sink.h"
|
||||
#include "spdlog/sinks/null_sink.h"
|
||||
#include "spdlog/sinks/rotating_file_sink.h"
|
||||
|
||||
void prepare_logdir()
|
||||
{
|
||||
spdlog::info("Preparing latency_logs directory..");
|
||||
#ifdef _WIN32
|
||||
system("if not exist logs mkdir latency_logs");
|
||||
system("del /F /Q logs\\*");
|
||||
#else
|
||||
auto rv = system("mkdir -p latency_logs");
|
||||
if (rv != 0)
|
||||
{
|
||||
throw std::runtime_error("Failed to mkdir -p latency_logs");
|
||||
}
|
||||
rv = system("rm -f latency_logs/*");
|
||||
if (rv != 0)
|
||||
{
|
||||
throw std::runtime_error("Failed to rm -f latency_logs/*");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void bench_c_string(benchmark::State &state, std::shared_ptr<spdlog::logger> logger)
|
||||
{
|
||||
const char *msg = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum pharetra metus cursus "
|
||||
"lacus placerat congue. Nulla egestas, mauris a tincidunt tempus, enim lectus volutpat mi, eu consequat sem "
|
||||
"libero nec massa. In dapibus ipsum a diam rhoncus gravida. Etiam non dapibus eros. Donec fringilla dui sed "
|
||||
"augue pretium, nec scelerisque est maximus. Nullam convallis, sem nec blandit maximus, nisi turpis ornare "
|
||||
"nisl, sit amet volutpat neque massa eu odio. Maecenas malesuada quam ex, posuere congue nibh turpis duis.";
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
logger->info(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void bench_logger(benchmark::State &state, std::shared_ptr<spdlog::logger> logger)
|
||||
{
|
||||
int i = 0;
|
||||
for (auto _ : state)
|
||||
{
|
||||
logger->info("Hello logger: msg number {}...............", ++i);
|
||||
}
|
||||
}
|
||||
|
||||
void bench_disabled_macro(benchmark::State &state, std::shared_ptr<spdlog::logger> logger)
|
||||
{
|
||||
int i = 0;
|
||||
benchmark::DoNotOptimize(i); // prevent unused warnings
|
||||
benchmark::DoNotOptimize(logger); // prevent unused warnings
|
||||
for (auto _ : state)
|
||||
{
|
||||
SPDLOG_LOGGER_DEBUG(logger, "Hello logger: msg number {}...............", i++);
|
||||
SPDLOG_DEBUG("Hello logger: msg number {}...............", i++);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
using spdlog::sinks::basic_file_sink_mt;
|
||||
using spdlog::sinks::basic_file_sink_st;
|
||||
using spdlog::sinks::null_sink_mt;
|
||||
using spdlog::sinks::null_sink_st;
|
||||
|
||||
size_t file_size = 30 * 1024 * 1024;
|
||||
size_t rotating_files = 5;
|
||||
int n_threads = benchmark::CPUInfo::Get().num_cpus;
|
||||
|
||||
prepare_logdir();
|
||||
|
||||
// disabled loggers
|
||||
auto disabled_logger = std::make_shared<spdlog::logger>("bench", std::make_shared<null_sink_mt>());
|
||||
disabled_logger->set_level(spdlog::level::off);
|
||||
benchmark::RegisterBenchmark("disabled-at-compile-time", bench_disabled_macro, disabled_logger);
|
||||
benchmark::RegisterBenchmark("disabled-at-runtime", bench_logger, disabled_logger);
|
||||
|
||||
auto null_logger_st = std::make_shared<spdlog::logger>("bench", std::make_shared<null_sink_st>());
|
||||
benchmark::RegisterBenchmark("null_sink_st (500_bytes c_str)", bench_c_string, std::move(null_logger_st));
|
||||
benchmark::RegisterBenchmark("null_sink_st", bench_logger, null_logger_st);
|
||||
|
||||
// basic_st
|
||||
auto basic_st = spdlog::basic_logger_st("basic_st", "latency_logs/basic_st.log", true);
|
||||
benchmark::RegisterBenchmark("basic_st", bench_logger, std::move(basic_st))->UseRealTime();
|
||||
spdlog::drop("basic_st");
|
||||
|
||||
// rotating st
|
||||
auto rotating_st = spdlog::rotating_logger_st("rotating_st", "latency_logs/rotating_st.log", file_size, rotating_files);
|
||||
benchmark::RegisterBenchmark("rotating_st", bench_logger, std::move(rotating_st))->UseRealTime();
|
||||
spdlog::drop("rotating_st");
|
||||
|
||||
// daily st
|
||||
auto daily_st = spdlog::daily_logger_mt("daily_st", "latency_logs/daily_st.log");
|
||||
benchmark::RegisterBenchmark("daily_st", bench_logger, std::move(daily_st))->UseRealTime();
|
||||
spdlog::drop("daily_st");
|
||||
|
||||
// //
|
||||
// // Multi threaded bench, 10 loggers using same logger concurrently
|
||||
// //
|
||||
auto null_logger_mt = std::make_shared<spdlog::logger>("bench", std::make_shared<null_sink_mt>());
|
||||
benchmark::RegisterBenchmark("null_sink_mt", bench_logger, null_logger_mt)->Threads(n_threads)->UseRealTime();
|
||||
|
||||
// basic_mt
|
||||
auto basic_mt = spdlog::basic_logger_mt("basic_mt", "latency_logs/basic_mt.log", true);
|
||||
benchmark::RegisterBenchmark("basic_mt", bench_logger, std::move(basic_mt))->Threads(n_threads)->UseRealTime();
|
||||
spdlog::drop("basic_mt");
|
||||
|
||||
// rotating mt
|
||||
auto rotating_mt = spdlog::rotating_logger_mt("rotating_mt", "latency_logs/rotating_mt.log", file_size, rotating_files);
|
||||
benchmark::RegisterBenchmark("rotating_mt", bench_logger, std::move(rotating_mt))->Threads(n_threads)->UseRealTime();
|
||||
spdlog::drop("rotating_mt");
|
||||
|
||||
// daily mt
|
||||
auto daily_mt = spdlog::daily_logger_mt("daily_mt", "latency_logs/daily_mt.log");
|
||||
benchmark::RegisterBenchmark("daily_mt", bench_logger, std::move(daily_mt))->Threads(n_threads)->UseRealTime();
|
||||
spdlog::drop("daily_mt");
|
||||
|
||||
// async
|
||||
auto queue_size = 1024 * 1024 * 3;
|
||||
auto tp = std::make_shared<spdlog::details::thread_pool>(queue_size, 1);
|
||||
auto async_logger = std::make_shared<spdlog::async_logger>(
|
||||
"async_logger", std::make_shared<null_sink_mt>(), std::move(tp), spdlog::async_overflow_policy::overrun_oldest);
|
||||
benchmark::RegisterBenchmark("async_logger", bench_logger, async_logger)->Threads(n_threads)->UseRealTime();
|
||||
|
||||
benchmark::Initialize(&argc, argv);
|
||||
benchmark::RunSpecifiedBenchmarks();
|
||||
}
|
4
third_party/spdlog/bench/logs/.gitignore
vendored
Normal file
4
third_party/spdlog/bench/logs/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
19
third_party/spdlog/bench/mem
vendored
Executable file
19
third_party/spdlog/bench/mem
vendored
Executable file
@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "usage: $0 <program>"
|
||||
fi
|
||||
|
||||
PROG=$1
|
||||
|
||||
if [ ! -x "$PROG" ]; then
|
||||
echo $PROG not found or not executable.
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$* &
|
||||
PID=$!
|
||||
|
||||
while `kill -0 $PID 2>/dev/null`; do
|
||||
ps -eo size,pid,user,pcpu,command --sort -size | awk '{ line=1 ; hr=$1/1024 ; printf("%13.2f Mb ",hr); } { for ( x=4 ; x<=NF ; x++ ) { printf("%s ",$x) } print "" }' | grep -v grep | grep -v $0 | grep $PROG
|
||||
done
|
34
third_party/spdlog/bench/utils.h
vendored
Normal file
34
third_party/spdlog/bench/utils.h
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
//
|
||||
// Copyright(c) 2015 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iomanip>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
|
||||
namespace utils {
|
||||
|
||||
template<typename T>
|
||||
inline std::string format(const T &value)
|
||||
{
|
||||
static std::locale loc("");
|
||||
std::stringstream ss;
|
||||
ss.imbue(loc);
|
||||
ss << value;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline std::string format(const double &value)
|
||||
{
|
||||
static std::locale loc("");
|
||||
std::stringstream ss;
|
||||
ss.imbue(loc);
|
||||
ss << std::fixed << std::setprecision(1) << value;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace utils
|
2
third_party/spdlog/clang_tidy.sh
vendored
Executable file
2
third_party/spdlog/clang_tidy.sh
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
clang-tidy example/example.cpp -- -I ./include
|
31
third_party/spdlog/cmake/Config.cmake.in
vendored
Normal file
31
third_party/spdlog/cmake/Config.cmake.in
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
# *************************************************************************/
|
||||
# * Copyright (c) 2015 Ruslan Baratov. */
|
||||
# * */
|
||||
# * Permission is hereby granted, free of charge, to any person obtaining */
|
||||
# * a copy of this software and associated documentation files (the */
|
||||
# * "Software"), to deal in the Software without restriction, including */
|
||||
# * without limitation the rights to use, copy, modify, merge, publish, */
|
||||
# * distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
# * permit persons to whom the Software is furnished to do so, subject to */
|
||||
# * the following conditions: */
|
||||
# * */
|
||||
# * The above copyright notice and this permission notice shall be */
|
||||
# * included in all copies or substantial portions of the Software. */
|
||||
# * */
|
||||
# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
# * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
# * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
# * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
# * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
# * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
# * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
# *************************************************************************/
|
||||
|
||||
set(SPDLOG_FMT_EXTERNAL @SPDLOG_FMT_EXTERNAL@)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake")
|
||||
|
||||
if(SPDLOG_FMT_EXTERNAL)
|
||||
include(CMakeFindDependencyMacro)
|
||||
find_dependency(fmt CONFIG)
|
||||
endif()
|
21
third_party/spdlog/cmake/sanitizers.cmake
vendored
Normal file
21
third_party/spdlog/cmake/sanitizers.cmake
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
if(SPDLOG_SANITIZE_THREAD AND SPDLOG_SANITIZE_ADDRESS)
|
||||
message(FATAL_ERROR "AddressSanitizer is not compatible with ThreadSanitizer.")
|
||||
endif()
|
||||
|
||||
if(SPDLOG_SANITIZE_ADDRESS)
|
||||
message(STATUS "AddressSanitizer enabled")
|
||||
set(SANITIZER_FLAGS "-fsanitize=address,undefined")
|
||||
add_compile_options("-fno-sanitize=signed-integer-overflow")
|
||||
endif()
|
||||
|
||||
if(SPDLOG_SANITIZE_THREAD)
|
||||
message(STATUS "ThreadSanitizer enabled")
|
||||
set(SANITIZER_FLAGS "-fsanitize=thread")
|
||||
endif()
|
||||
|
||||
if(SPDLOG_SANITIZE_THREAD OR SPDLOG_SANITIZE_ADDRESS)
|
||||
add_compile_options(${SANITIZER_FLAGS})
|
||||
add_compile_options("-fno-sanitize-recover=all")
|
||||
add_compile_options("-fno-omit-frame-pointer")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${SANITIZER_FLAGS} -fuse-ld=gold")
|
||||
endif()
|
6
third_party/spdlog/cmake/spdlog.pc.in
vendored
Normal file
6
third_party/spdlog/cmake/spdlog.pc.in
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: @PROJECT_NAME@
|
||||
Description: Super fast C++ logging library.
|
||||
Version: @PROJECT_VERSION@
|
49
third_party/spdlog/example/CMakeLists.txt
vendored
Normal file
49
third_party/spdlog/example/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
# *************************************************************************/
|
||||
# * Copyright (c) 2015 Ruslan Baratov. */
|
||||
# * */
|
||||
# * Permission is hereby granted, free of charge, to any person obtaining */
|
||||
# * a copy of this software and associated documentation files (the */
|
||||
# * "Software"), to deal in the Software without restriction, including */
|
||||
# * without limitation the rights to use, copy, modify, merge, publish, */
|
||||
# * distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
# * permit persons to whom the Software is furnished to do so, subject to */
|
||||
# * the following conditions: */
|
||||
# * */
|
||||
# * The above copyright notice and this permission notice shall be */
|
||||
# * included in all copies or substantial portions of the Software. */
|
||||
# * */
|
||||
# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
# * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
# * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
# * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
# * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
# * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
# * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
# *************************************************************************/
|
||||
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
project(SpdlogExamples CXX)
|
||||
|
||||
if(NOT TARGET spdlog)
|
||||
# Stand-alone build
|
||||
find_package(spdlog CONFIG REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_executable(example example.cpp)
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Android")
|
||||
find_library(log-lib log)
|
||||
target_link_libraries(example spdlog::spdlog Threads::Threads log)
|
||||
else()
|
||||
target_link_libraries(example spdlog::spdlog Threads::Threads)
|
||||
endif()
|
||||
|
||||
|
||||
add_executable(multisink multisink.cpp)
|
||||
target_link_libraries(multisink spdlog::spdlog Threads::Threads)
|
||||
|
||||
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/logs")
|
||||
|
||||
enable_testing()
|
||||
add_test(NAME example COMMAND example)
|
22
third_party/spdlog/example/Makefile-all-warn
vendored
Normal file
22
third_party/spdlog/example/Makefile-all-warn
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
#-Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded
|
||||
CXX ?= g++
|
||||
CXX_FLAGS = -Wall -Wextra -pedantic -std=c++11 -pthread -I../include -fmax-errors=1 -Wconversion -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded -Wno-weak-vtables -Wno-global-constructors
|
||||
CXX_RELEASE_FLAGS = -O3 -march=native
|
||||
CXX_DEBUG_FLAGS= -g
|
||||
|
||||
all: example
|
||||
debug: example-debug
|
||||
|
||||
example: example.cpp
|
||||
$(CXX) example.cpp -o example $(CXX_FLAGS) $(CXX_RELEASE_FLAGS) $(CXXFLAGS)
|
||||
|
||||
|
||||
example-debug: example.cpp
|
||||
$(CXX) example.cpp -o example-debug $(CXX_FLAGS) $(CXX_DEBUG_FLAGS) $(CXXFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f *.o logs/*.txt example example-debug
|
||||
|
||||
|
||||
rebuild: clean all
|
||||
rebuild-debug: clean debug
|
26
third_party/spdlog/example/Makefile.clang
vendored
Normal file
26
third_party/spdlog/example/Makefile.clang
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
CXX = clang++
|
||||
CXXFLAGS = -march=native -Wall -Wextra -Wshadow -pedantic -std=c++11 -pthread -I../include
|
||||
CXX_RELEASE_FLAGS = -O2
|
||||
CXX_DEBUG_FLAGS= -g
|
||||
|
||||
|
||||
all: example
|
||||
debug: example-debug
|
||||
|
||||
example: example.cpp
|
||||
$(CXX) example.cpp -o example-clang $(CXXFLAGS) $(CXX_RELEASE_FLAGS)
|
||||
|
||||
|
||||
|
||||
example-debug: example.cpp
|
||||
$(CXX) example.cpp -o example-clang-debug $(CXXFLAGS) $(CXX_DEBUG_FLAGS)
|
||||
|
||||
|
||||
clean:
|
||||
rm -f *.o logs/*.txt example-clang example-clang-debug
|
||||
|
||||
|
||||
rebuild: clean all
|
||||
rebuild-debug: clean debug
|
||||
|
||||
|
25
third_party/spdlog/example/Makefile.mingw
vendored
Normal file
25
third_party/spdlog/example/Makefile.mingw
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
CXX ?= g++
|
||||
CXXFLAGS = -D_WIN32_WINNT=0x600 -march=native -Wall -Wextra -pedantic -std=gnu++0x -pthread -Wl,--no-as-needed -I../include
|
||||
CXX_RELEASE_FLAGS = -O3
|
||||
CXX_DEBUG_FLAGS= -g
|
||||
|
||||
|
||||
all: example
|
||||
debug: example-debug
|
||||
|
||||
example: example.cpp
|
||||
$(CXX) example.cpp -o example $(CXXFLAGS) $(CXX_RELEASE_FLAGS)
|
||||
|
||||
|
||||
example-debug: example.cpp
|
||||
$(CXX) example.cpp -o example-debug $(CXXFLAGS) $(CXX_DEBUG_FLAGS)
|
||||
|
||||
|
||||
clean:
|
||||
rm -f *.o logs/*.txt example example-debug
|
||||
|
||||
|
||||
rebuild: clean all
|
||||
rebuild-debug: clean debug
|
||||
|
||||
|
235
third_party/spdlog/example/example.cpp
vendored
Normal file
235
third_party/spdlog/example/example.cpp
vendored
Normal file
@ -0,0 +1,235 @@
|
||||
//
|
||||
// Copyright(c) 2015 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
//
|
||||
// spdlog usage example
|
||||
//
|
||||
//
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
void stdout_logger_example();
|
||||
void basic_example();
|
||||
void rotating_example();
|
||||
void daily_example();
|
||||
void async_example();
|
||||
void binary_example();
|
||||
void trace_example();
|
||||
void multi_sink_example();
|
||||
void user_defined_example();
|
||||
void err_handler_example();
|
||||
void syslog_example();
|
||||
void clone_example();
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
int main(int, char *[])
|
||||
{
|
||||
spdlog::info("Welcome to spdlog version {}.{}.{} !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH);
|
||||
spdlog::warn("Easy padding in numbers like {:08d}", 12);
|
||||
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
|
||||
spdlog::info("Support for floats {:03.2f}", 1.23456);
|
||||
spdlog::info("Positional args are {1} {0}..", "too", "supported");
|
||||
spdlog::info("{:>8} aligned, {:<8} aligned", "right", "left");
|
||||
|
||||
// Runtime log levels
|
||||
spdlog::set_level(spdlog::level::info); // Set global log level to info
|
||||
spdlog::debug("This message should not be displayed!");
|
||||
spdlog::set_level(spdlog::level::trace); // Set specific logger's log level
|
||||
spdlog::debug("This message should be displayed..");
|
||||
|
||||
// Customize msg format for all loggers
|
||||
spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [thread %t] %v");
|
||||
spdlog::info("This an info message with custom format");
|
||||
spdlog::set_pattern("%+"); // back to default format
|
||||
|
||||
try
|
||||
{
|
||||
stdout_logger_example();
|
||||
basic_example();
|
||||
rotating_example();
|
||||
daily_example();
|
||||
clone_example();
|
||||
async_example();
|
||||
binary_example();
|
||||
multi_sink_example();
|
||||
user_defined_example();
|
||||
err_handler_example();
|
||||
trace_example();
|
||||
|
||||
// Flush all *registered* loggers using a worker thread every 3 seconds.
|
||||
// note: registered loggers *must* be thread safe for this to work correctly!
|
||||
spdlog::flush_every(std::chrono::seconds(3));
|
||||
|
||||
// Apply some function on all registered loggers
|
||||
spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) { l->info("End of example."); });
|
||||
|
||||
// Release all spdlog resources, and drop all loggers in the registry.
|
||||
// This is optional (only mandatory if using windows + async log).
|
||||
spdlog::shutdown();
|
||||
}
|
||||
|
||||
// Exceptions will only be thrown upon failed logger or sink construction (not during logging).
|
||||
catch (const spdlog::spdlog_ex &ex)
|
||||
{
|
||||
std::printf("Log initialization failed: %s\n", ex.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
// or #include "spdlog/sinks/stdout_sinks.h" if no colors needed.
|
||||
void stdout_logger_example()
|
||||
{
|
||||
// Create color multi threaded logger.
|
||||
auto console = spdlog::stdout_color_mt("console");
|
||||
// or for stderr:
|
||||
// auto console = spdlog::stderr_color_mt("error-logger");
|
||||
}
|
||||
|
||||
#include "spdlog/sinks/basic_file_sink.h"
|
||||
void basic_example()
|
||||
{
|
||||
// Create basic file logger (not rotated).
|
||||
auto my_logger = spdlog::basic_logger_mt("file_logger", "logs/basic-log.txt");
|
||||
}
|
||||
|
||||
#include "spdlog/sinks/rotating_file_sink.h"
|
||||
void rotating_example()
|
||||
{
|
||||
// Create a file rotating logger with 5mb size max and 3 rotated files.
|
||||
auto rotating_logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3);
|
||||
}
|
||||
|
||||
#include "spdlog/sinks/daily_file_sink.h"
|
||||
void daily_example()
|
||||
{
|
||||
// Create a daily logger - a new file is created every day on 2:30am.
|
||||
auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);
|
||||
}
|
||||
|
||||
// Clone a logger and give it new name.
|
||||
// Useful for creating component/subsystem loggers from some "root" logger.
|
||||
void clone_example()
|
||||
{
|
||||
auto network_logger = spdlog::default_logger()->clone("network");
|
||||
network_logger->info("Logging network stuff..");
|
||||
}
|
||||
|
||||
#include "spdlog/async.h"
|
||||
void async_example()
|
||||
{
|
||||
// Default thread pool settings can be modified *before* creating the async logger:
|
||||
// spdlog::init_thread_pool(32768, 1); // queue with max 32k items 1 backing thread.
|
||||
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async_log.txt");
|
||||
// alternatively:
|
||||
// auto async_file = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("async_file_logger", "logs/async_log.txt");
|
||||
|
||||
for (int i = 1; i < 101; ++i)
|
||||
{
|
||||
async_file->info("Async message #{}", i);
|
||||
}
|
||||
}
|
||||
|
||||
// Log binary data as hex.
|
||||
// Many types of std::container<char> types can be used.
|
||||
// Iterator ranges are supported too.
|
||||
// Format flags:
|
||||
// {:X} - print in uppercase.
|
||||
// {:s} - don't separate each byte with space.
|
||||
// {:p} - don't print the position on each line start.
|
||||
// {:n} - don't split the output to lines.
|
||||
|
||||
#include "spdlog/fmt/bin_to_hex.h"
|
||||
void binary_example()
|
||||
{
|
||||
std::vector<char> buf;
|
||||
for (int i = 0; i < 80; i++)
|
||||
{
|
||||
buf.push_back(static_cast<char>(i & 0xff));
|
||||
}
|
||||
spdlog::info("Binary example: {}", spdlog::to_hex(buf));
|
||||
spdlog::info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10));
|
||||
// more examples:
|
||||
// logger->info("uppercase: {:X}", spdlog::to_hex(buf));
|
||||
// logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf));
|
||||
// logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf));
|
||||
}
|
||||
|
||||
// Compile time log levels.
|
||||
// define SPDLOG_ACTIVE_LEVEL to required level (e.g. SPDLOG_LEVEL_TRACE)
|
||||
void trace_example()
|
||||
{
|
||||
// trace from default logger
|
||||
SPDLOG_TRACE("Some trace message.. {} ,{}", 1, 3.23);
|
||||
// debug from default logger
|
||||
SPDLOG_DEBUG("Some debug message.. {} ,{}", 1, 3.23);
|
||||
|
||||
// trace from logger object
|
||||
auto logger = spdlog::get("file_logger");
|
||||
SPDLOG_LOGGER_TRACE(logger, "another trace message");
|
||||
}
|
||||
|
||||
// A logger with multiple sinks (stdout and file) - each with a different format and log level.
|
||||
void multi_sink_example()
|
||||
{
|
||||
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
||||
console_sink->set_level(spdlog::level::warn);
|
||||
console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v");
|
||||
|
||||
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/multisink.txt", true);
|
||||
file_sink->set_level(spdlog::level::trace);
|
||||
|
||||
spdlog::logger logger("multi_sink", {console_sink, file_sink});
|
||||
logger.set_level(spdlog::level::debug);
|
||||
logger.warn("this should appear in both console and file");
|
||||
logger.info("this message should not appear in the console, only in the file");
|
||||
}
|
||||
|
||||
// User defined types logging by implementing operator<<
|
||||
#include "spdlog/fmt/ostr.h" // must be included
|
||||
struct my_type
|
||||
{
|
||||
int i;
|
||||
template<typename OStream>
|
||||
friend OStream &operator<<(OStream &os, const my_type &c)
|
||||
{
|
||||
return os << "[my_type i=" << c.i << "]";
|
||||
}
|
||||
};
|
||||
|
||||
void user_defined_example()
|
||||
{
|
||||
spdlog::info("user defined type: {}", my_type{14});
|
||||
}
|
||||
|
||||
// Custom error handler. Will be triggered on log failure.
|
||||
void err_handler_example()
|
||||
{
|
||||
// can be set globally or per logger(logger->set_error_handler(..))
|
||||
spdlog::set_error_handler([](const std::string &msg) { printf("*** Custom log error handler: %s ***\n", msg.c_str()); });
|
||||
}
|
||||
|
||||
// syslog example (linux/osx/freebsd)
|
||||
#ifndef _WIN32
|
||||
#include "spdlog/sinks/syslog_sink.h"
|
||||
void syslog_example()
|
||||
{
|
||||
std::string ident = "spdlog-example";
|
||||
auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID);
|
||||
syslog_logger->warn("This is warning that will end up in syslog.");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Android example.
|
||||
#if defined(__ANDROID__)
|
||||
#include "spdlog/sinks/android_sink.h"
|
||||
void android_example()
|
||||
{
|
||||
std::string tag = "spdlog-android";
|
||||
auto android_logger = spdlog::android_logger_mt("android", tag);
|
||||
android_logger->critical("Use \"adb shell logcat\" to view this message.");
|
||||
}
|
||||
|
||||
#endif
|
106
third_party/spdlog/example/example.sln
vendored
Normal file
106
third_party/spdlog/example/example.sln
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27703.2018
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example", "example.vcxproj", "{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "spdlog", "spdlog", "{7FC6AB76-AD88-4135-888C-0568E81475AF}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\include\spdlog\async.h = ..\include\spdlog\async.h
|
||||
..\include\spdlog\async_logger.h = ..\include\spdlog\async_logger.h
|
||||
..\include\spdlog\common.h = ..\include\spdlog\common.h
|
||||
..\include\spdlog\formatter.h = ..\include\spdlog\formatter.h
|
||||
..\include\spdlog\logger.h = ..\include\spdlog\logger.h
|
||||
..\include\spdlog\spdlog.h = ..\include\spdlog\spdlog.h
|
||||
..\include\spdlog\tweakme.h = ..\include\spdlog\tweakme.h
|
||||
..\include\spdlog\version.h = ..\include\spdlog\version.h
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "details", "details", "{08E93803-E650-42D9-BBB4-3C16979F850E}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\include\spdlog\details\async_logger_impl.h = ..\include\spdlog\details\async_logger_impl.h
|
||||
..\include\spdlog\details\circular_q.h = ..\include\spdlog\details\circular_q.h
|
||||
..\include\spdlog\details\console_globals.h = ..\include\spdlog\details\console_globals.h
|
||||
..\include\spdlog\details\file_helper.h = ..\include\spdlog\details\file_helper.h
|
||||
..\include\spdlog\details\fmt_helper.h = ..\include\spdlog\details\fmt_helper.h
|
||||
..\include\spdlog\details\log_msg.h = ..\include\spdlog\details\log_msg.h
|
||||
..\include\spdlog\details\logger_impl.h = ..\include\spdlog\details\logger_impl.h
|
||||
..\include\spdlog\details\mpmc_blocking_q.h = ..\include\spdlog\details\mpmc_blocking_q.h
|
||||
..\include\spdlog\details\null_mutex.h = ..\include\spdlog\details\null_mutex.h
|
||||
..\include\spdlog\details\os.h = ..\include\spdlog\details\os.h
|
||||
..\include\spdlog\details\pattern_formatter.h = ..\include\spdlog\details\pattern_formatter.h
|
||||
..\include\spdlog\details\periodic_worker.h = ..\include\spdlog\details\periodic_worker.h
|
||||
..\include\spdlog\details\registry.h = ..\include\spdlog\details\registry.h
|
||||
..\include\spdlog\details\spdlog_impl.h = ..\include\spdlog\details\spdlog_impl.h
|
||||
..\include\spdlog\details\thread_pool.h = ..\include\spdlog\details\thread_pool.h
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fmt", "fmt", "{82378DE1-8463-4F91-91A0-C2C40E2AEA2A}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\include\spdlog\fmt\fmt.h = ..\include\spdlog\fmt\fmt.h
|
||||
..\include\spdlog\fmt\ostr.h = ..\include\spdlog\fmt\ostr.h
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bundled", "bundled", "{D9CA4494-80D1-48D1-A897-D3564F7B27FF}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\include\spdlog\fmt\bundled\format.cc = ..\include\spdlog\fmt\bundled\format.cc
|
||||
..\include\spdlog\fmt\bundled\format.h = ..\include\spdlog\fmt\bundled\format.h
|
||||
..\include\spdlog\fmt\bundled\LICENSE.rst = ..\include\spdlog\fmt\bundled\LICENSE.rst
|
||||
..\include\spdlog\fmt\bundled\ostream.cc = ..\include\spdlog\fmt\bundled\ostream.cc
|
||||
..\include\spdlog\fmt\bundled\ostream.h = ..\include\spdlog\fmt\bundled\ostream.h
|
||||
..\include\spdlog\fmt\bundled\posix.cc = ..\include\spdlog\fmt\bundled\posix.cc
|
||||
..\include\spdlog\fmt\bundled\posix.h = ..\include\spdlog\fmt\bundled\posix.h
|
||||
..\include\spdlog\fmt\bundled\printf.cc = ..\include\spdlog\fmt\bundled\printf.cc
|
||||
..\include\spdlog\fmt\bundled\printf.h = ..\include\spdlog\fmt\bundled\printf.h
|
||||
..\include\spdlog\fmt\bundled\time.h = ..\include\spdlog\fmt\bundled\time.h
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sinks", "sinks", "{27D16BB9-2B81-4F61-80EC-0C7A777248E4}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\include\spdlog\sinks\android_sink.h = ..\include\spdlog\sinks\android_sink.h
|
||||
..\include\spdlog\sinks\ansicolor_sink.h = ..\include\spdlog\sinks\ansicolor_sink.h
|
||||
..\include\spdlog\sinks\base_sink.h = ..\include\spdlog\sinks\base_sink.h
|
||||
..\include\spdlog\sinks\dist_sink.h = ..\include\spdlog\sinks\dist_sink.h
|
||||
..\include\spdlog\sinks\file_sinks.h = ..\include\spdlog\sinks\file_sinks.h
|
||||
..\include\spdlog\sinks\msvc_sink.h = ..\include\spdlog\sinks\msvc_sink.h
|
||||
..\include\spdlog\sinks\null_sink.h = ..\include\spdlog\sinks\null_sink.h
|
||||
..\include\spdlog\sinks\ostream_sink.h = ..\include\spdlog\sinks\ostream_sink.h
|
||||
..\include\spdlog\sinks\sink.h = ..\include\spdlog\sinks\sink.h
|
||||
..\include\spdlog\sinks\stdout_color_sinks.h = ..\include\spdlog\sinks\stdout_color_sinks.h
|
||||
..\include\spdlog\sinks\stdout_sinks.h = ..\include\spdlog\sinks\stdout_sinks.h
|
||||
..\include\spdlog\sinks\syslog_sink.h = ..\include\spdlog\sinks\syslog_sink.h
|
||||
..\include\spdlog\sinks\wincolor_sink.h = ..\include\spdlog\sinks\wincolor_sink.h
|
||||
..\include\spdlog\sinks\windebug_sink.h = ..\include\spdlog\sinks\windebug_sink.h
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Win32 = Release|Win32
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Debug|x64.Build.0 = Debug|x64
|
||||
{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Release|Win32.Build.0 = Release|Win32
|
||||
{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Release|x64.ActiveCfg = Release|x64
|
||||
{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{08E93803-E650-42D9-BBB4-3C16979F850E} = {7FC6AB76-AD88-4135-888C-0568E81475AF}
|
||||
{82378DE1-8463-4F91-91A0-C2C40E2AEA2A} = {7FC6AB76-AD88-4135-888C-0568E81475AF}
|
||||
{D9CA4494-80D1-48D1-A897-D3564F7B27FF} = {82378DE1-8463-4F91-91A0-C2C40E2AEA2A}
|
||||
{27D16BB9-2B81-4F61-80EC-0C7A777248E4} = {7FC6AB76-AD88-4135-888C-0568E81475AF}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {1BF53532-C5DC-4236-B195-9E17CBE40A48}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
167
third_party/spdlog/example/example.vcxproj
vendored
Normal file
167
third_party/spdlog/example/example.vcxproj
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="example.cpp" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>.</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeaderFile />
|
||||
<PrecompiledHeaderOutputFile />
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeaderFile>
|
||||
</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>
|
||||
</PrecompiledHeaderOutputFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeaderFile />
|
||||
<PrecompiledHeaderOutputFile />
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeaderFile>
|
||||
</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>
|
||||
</PrecompiledHeaderOutputFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
15
third_party/spdlog/example/jni/Android.mk
vendored
Normal file
15
third_party/spdlog/example/jni/Android.mk
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# Setup a project
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := example
|
||||
LOCAL_SRC_FILES := example.cpp
|
||||
LOCAL_CPPFLAGS += -Wall -Wshadow -Wextra -pedantic -std=c++11 -fPIE -pie
|
||||
LOCAL_LDFLAGS += -fPIE -pie
|
||||
|
||||
# Add exception support and set path for spdlog's headers
|
||||
LOCAL_CPPFLAGS += -fexceptions -I../include
|
||||
# Use android's log library
|
||||
LOCAL_LDFLAGS += -llog
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
2
third_party/spdlog/example/jni/Application.mk
vendored
Normal file
2
third_party/spdlog/example/jni/Application.mk
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Exceptions are used in spdlog. Link to an exception-ready C++ runtime.
|
||||
APP_STL = gnustl_static
|
157
third_party/spdlog/example/jni/example.cpp
vendored
Normal file
157
third_party/spdlog/example/jni/example.cpp
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
//
|
||||
// Copyright(c) 2015 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
//
|
||||
// spdlog usage example
|
||||
//
|
||||
//
|
||||
|
||||
#define SPDLOG_TRACE_ON
|
||||
#define SPDLOG_DEBUG_ON
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
void async_example();
|
||||
void syslog_example();
|
||||
void android_example();
|
||||
void user_defined_example();
|
||||
void err_handler_example();
|
||||
|
||||
namespace spd = spdlog;
|
||||
int main(int, char *[])
|
||||
{
|
||||
try
|
||||
{
|
||||
// Console logger with color
|
||||
auto console = spd::stdout_color_mt("console");
|
||||
console->info("Welcome to spdlog!");
|
||||
console->error("Some error message with arg{}..", 1);
|
||||
|
||||
// Formatting examples
|
||||
console->warn("Easy padding in numbers like {:08d}", 12);
|
||||
console->critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
|
||||
console->info("Support for floats {:03.2f}", 1.23456);
|
||||
console->info("Positional args are {1} {0}..", "too", "supported");
|
||||
console->info("{:<30}", "left aligned");
|
||||
|
||||
spd::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name) function");
|
||||
|
||||
// Create basic file logger (not rotated)
|
||||
auto my_logger = spd::basic_logger_mt("basic_logger", "logs/basic-log.txt");
|
||||
my_logger->info("Some log message");
|
||||
|
||||
// Create a file rotating logger with 5mb size max and 3 rotated files
|
||||
auto rotating_logger = spd::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3);
|
||||
for (int i = 0; i < 10; ++i)
|
||||
rotating_logger->info("{} * {} equals {:>10}", i, i, i * i);
|
||||
|
||||
// Create a daily logger - a new file is created every day on 2:30am
|
||||
auto daily_logger = spd::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);
|
||||
// trigger flush if the log severity is error or higher
|
||||
daily_logger->flush_on(spd::level::err);
|
||||
daily_logger->info(123.44);
|
||||
|
||||
// Customize msg format for all messages
|
||||
spd::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***");
|
||||
rotating_logger->info("This is another message with custom format");
|
||||
|
||||
// Runtime log levels
|
||||
spd::set_level(spd::level::info); // Set global log level to info
|
||||
console->debug("This message should not be displayed!");
|
||||
console->set_level(spd::level::debug); // Set specific logger's log level
|
||||
console->debug("This message should be displayed..");
|
||||
|
||||
// Compile time log levels
|
||||
// define SPDLOG_DEBUG_ON or SPDLOG_TRACE_ON
|
||||
SPDLOG_TRACE(console, "Enabled only #ifdef SPDLOG_TRACE_ON..{} ,{}", 1, 3.23);
|
||||
SPDLOG_DEBUG(console, "Enabled only #ifdef SPDLOG_DEBUG_ON.. {} ,{}", 1, 3.23);
|
||||
|
||||
// Asynchronous logging is very fast..
|
||||
// Just call spdlog::set_async_mode(q_size) and all created loggers from now on will be asynchronous..
|
||||
async_example();
|
||||
|
||||
// syslog example. linux/osx only
|
||||
syslog_example();
|
||||
|
||||
// android example. compile with NDK
|
||||
android_example();
|
||||
|
||||
// Log user-defined types example
|
||||
user_defined_example();
|
||||
|
||||
// Change default log error handler
|
||||
err_handler_example();
|
||||
|
||||
// Apply a function on all registered loggers
|
||||
spd::apply_all([&](std::shared_ptr<spdlog::logger> l) { l->info("End of example."); });
|
||||
|
||||
// Release and close all loggers
|
||||
spdlog::drop_all();
|
||||
}
|
||||
// Exceptions will only be thrown upon failed logger or sink construction (not during logging)
|
||||
catch (const spd::spdlog_ex &ex)
|
||||
{
|
||||
std::cout << "Log init failed: " << ex.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void async_example()
|
||||
{
|
||||
size_t q_size = 4096; // queue size must be power of 2
|
||||
spdlog::set_async_mode(q_size);
|
||||
auto async_file = spd::daily_logger_st("async_file_logger", "logs/async_log.txt");
|
||||
for (int i = 0; i < 100; ++i)
|
||||
async_file->info("Async message #{}", i);
|
||||
}
|
||||
|
||||
// syslog example (linux/osx/freebsd)
|
||||
void syslog_example()
|
||||
{
|
||||
#ifdef SPDLOG_ENABLE_SYSLOG
|
||||
std::string ident = "spdlog-example";
|
||||
auto syslog_logger = spd::syslog_logger("syslog", ident, LOG_PID);
|
||||
syslog_logger->warn("This is warning that will end up in syslog.");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Android example
|
||||
void android_example()
|
||||
{
|
||||
#if defined(__ANDROID__)
|
||||
std::string tag = "spdlog-android";
|
||||
auto android_logger = spd::android_logger("android", tag);
|
||||
android_logger->critical("Use \"adb shell logcat\" to view this message.");
|
||||
#endif
|
||||
}
|
||||
|
||||
// user defined types logging by implementing operator<<
|
||||
struct my_type
|
||||
{
|
||||
int i;
|
||||
template<typename OStream>
|
||||
friend OStream &operator<<(OStream &os, const my_type &c)
|
||||
{
|
||||
return os << "[my_type i=" << c.i << "]";
|
||||
}
|
||||
};
|
||||
|
||||
#include "spdlog/fmt/ostr.h" // must be included
|
||||
void user_defined_example()
|
||||
{
|
||||
spd::get("console")->info("user defined type: {}", my_type{14});
|
||||
}
|
||||
|
||||
//
|
||||
// custom error handler
|
||||
//
|
||||
void err_handler_example()
|
||||
{
|
||||
// can be set globaly or per logger(logger->set_error_handler(..))
|
||||
spdlog::set_error_handler([](const std::string &msg) { std::cerr << "my err handler: " << msg << std::endl; });
|
||||
spd::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3);
|
||||
}
|
47
third_party/spdlog/example/multisink.cpp
vendored
Normal file
47
third_party/spdlog/example/multisink.cpp
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "spdlog/sinks/basic_file_sink.h"
|
||||
#include "spdlog/sinks/stdout_sinks.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
int main(int, char *[])
|
||||
{
|
||||
bool enable_debug = true;
|
||||
try
|
||||
{
|
||||
// This other example use a single logger with multiple sinks.
|
||||
// This means that the same log_msg is forwarded to multiple sinks;
|
||||
// Each sink can have it's own log level and a message will be logged.
|
||||
std::vector<spdlog::sink_ptr> sinks;
|
||||
sinks.push_back(std::make_shared<spdlog::sinks::stdout_sink_mt>());
|
||||
sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>("./log_regular_file.txt"));
|
||||
sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>("./log_debug_file.txt"));
|
||||
|
||||
spdlog::logger console_multisink("multisink", sinks.begin(), sinks.end());
|
||||
console_multisink.set_level(spdlog::level::warn);
|
||||
|
||||
sinks[0]->set_level(spdlog::level::trace); // console. Allow everything. Default value
|
||||
sinks[1]->set_level(spdlog::level::trace); // regular file. Allow everything. Default value
|
||||
sinks[2]->set_level(spdlog::level::off); // regular file. Ignore everything.
|
||||
|
||||
console_multisink.warn("warn: will print only on console and regular file");
|
||||
|
||||
if (enable_debug)
|
||||
{
|
||||
console_multisink.set_level(spdlog::level::debug); // level of the logger
|
||||
sinks[1]->set_level(spdlog::level::debug); // regular file
|
||||
sinks[2]->set_level(spdlog::level::debug); // debug file
|
||||
}
|
||||
console_multisink.debug("Debug: you should see this on console and both files");
|
||||
|
||||
// Release and close all loggers
|
||||
spdlog::drop_all();
|
||||
}
|
||||
// Exceptions will only be thrown upon failed logger or sink construction (not during logging)
|
||||
catch (const spdlog::spdlog_ex &ex)
|
||||
{
|
||||
std::cout << "Log init failed: " << ex.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
34
third_party/spdlog/example/utils.h
vendored
Normal file
34
third_party/spdlog/example/utils.h
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
//
|
||||
// Copyright(c) 2015 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iomanip>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
|
||||
namespace utils {
|
||||
|
||||
template<typename T>
|
||||
inline std::string format(const T &value)
|
||||
{
|
||||
static std::locale loc("");
|
||||
std::stringstream ss;
|
||||
ss.imbue(loc);
|
||||
ss << value;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline std::string format(const double &value)
|
||||
{
|
||||
static std::locale loc("");
|
||||
std::stringstream ss;
|
||||
ss.imbue(loc);
|
||||
ss << std::fixed << std::setprecision(1) << value;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace utils
|
9
third_party/spdlog/format.sh
vendored
Executable file
9
third_party/spdlog/format.sh
vendored
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
echo -n "Running dos2unix "
|
||||
find . -name "*\.h" -o -name "*\.cpp"|grep -v bundled|xargs -I {} sh -c "dos2unix '{}' 2>/dev/null; echo -n '.'"
|
||||
echo
|
||||
echo -n "Running clang-format "
|
||||
find . -name "*\.h" -o -name "*\.cpp"|grep -v bundled|xargs -I {} sh -c "clang-format -i {}; echo -n '.'"
|
||||
echo
|
||||
|
||||
|
87
third_party/spdlog/include/spdlog/async.h
vendored
Normal file
87
third_party/spdlog/include/spdlog/async.h
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
|
||||
//
|
||||
// Copyright(c) 2018 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// Async logging using global thread pool
|
||||
// All loggers created here share same global thread pool.
|
||||
// Each log message is pushed to a queue along withe a shared pointer to the
|
||||
// logger.
|
||||
// If a logger deleted while having pending messages in the queue, it's actual
|
||||
// destruction will defer
|
||||
// until all its messages are processed by the thread pool.
|
||||
// This is because each message in the queue holds a shared_ptr to the
|
||||
// originating logger.
|
||||
|
||||
#include "spdlog/async_logger.h"
|
||||
#include "spdlog/details/registry.h"
|
||||
#include "spdlog/details/thread_pool.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace spdlog {
|
||||
|
||||
namespace details {
|
||||
static const size_t default_async_q_size = 8192;
|
||||
}
|
||||
|
||||
// async logger factory - creates async loggers backed with thread pool.
|
||||
// if a global thread pool doesn't already exist, create it with default queue
|
||||
// size of 8192 items and single thread.
|
||||
template<async_overflow_policy OverflowPolicy = async_overflow_policy::block>
|
||||
struct async_factory_impl
|
||||
{
|
||||
template<typename Sink, typename... SinkArgs>
|
||||
static std::shared_ptr<async_logger> create(std::string logger_name, SinkArgs &&... args)
|
||||
{
|
||||
auto ®istry_inst = details::registry::instance();
|
||||
|
||||
// create global thread pool if not already exists..
|
||||
std::lock_guard<std::recursive_mutex> tp_lock(registry_inst.tp_mutex());
|
||||
auto tp = registry_inst.get_tp();
|
||||
if (tp == nullptr)
|
||||
{
|
||||
tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1);
|
||||
registry_inst.set_tp(tp);
|
||||
}
|
||||
|
||||
auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...);
|
||||
auto new_logger = std::make_shared<async_logger>(std::move(logger_name), std::move(sink), std::move(tp), OverflowPolicy);
|
||||
registry_inst.initialize_logger(new_logger);
|
||||
return new_logger;
|
||||
}
|
||||
};
|
||||
|
||||
using async_factory = async_factory_impl<async_overflow_policy::block>;
|
||||
using async_factory_nonblock = async_factory_impl<async_overflow_policy::overrun_oldest>;
|
||||
|
||||
template<typename Sink, typename... SinkArgs>
|
||||
inline std::shared_ptr<spdlog::logger> create_async(std::string logger_name, SinkArgs &&... sink_args)
|
||||
{
|
||||
return async_factory::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...);
|
||||
}
|
||||
|
||||
template<typename Sink, typename... SinkArgs>
|
||||
inline std::shared_ptr<spdlog::logger> create_async_nb(std::string logger_name, SinkArgs &&... sink_args)
|
||||
{
|
||||
return async_factory_nonblock::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...);
|
||||
}
|
||||
|
||||
// set global thread pool.
|
||||
inline void init_thread_pool(size_t q_size, size_t thread_count)
|
||||
{
|
||||
auto tp = std::make_shared<details::thread_pool>(q_size, thread_count);
|
||||
details::registry::instance().set_tp(std::move(tp));
|
||||
}
|
||||
|
||||
// get the global thread pool.
|
||||
inline std::shared_ptr<spdlog::details::thread_pool> thread_pool()
|
||||
{
|
||||
return details::registry::instance().get_tp();
|
||||
}
|
||||
} // namespace spdlog
|
73
third_party/spdlog/include/spdlog/async_logger.h
vendored
Normal file
73
third_party/spdlog/include/spdlog/async_logger.h
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
//
|
||||
// Copyright(c) 2015 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
// Very fast asynchronous logger (millions of logs per second on an average
|
||||
// desktop)
|
||||
// Uses pre allocated lockfree queue for maximum throughput even under large
|
||||
// number of threads.
|
||||
// Creates a single back thread to pop messages from the queue and log them.
|
||||
//
|
||||
// Upon each log write the logger:
|
||||
// 1. Checks if its log level is enough to log the message
|
||||
// 2. Push a new copy of the message to a queue (or block the caller until
|
||||
// space is available in the queue)
|
||||
// 3. will throw spdlog_ex upon log exceptions
|
||||
// Upon destruction, logs all remaining messages in the queue before
|
||||
// destructing..
|
||||
|
||||
#include "spdlog/common.h"
|
||||
#include "spdlog/logger.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace spdlog {
|
||||
|
||||
// Async overflow policy - block by default.
|
||||
enum class async_overflow_policy
|
||||
{
|
||||
block, // Block until message can be enqueued
|
||||
overrun_oldest // Discard oldest message in the queue if full when trying to
|
||||
// add new item.
|
||||
};
|
||||
|
||||
namespace details {
|
||||
class thread_pool;
|
||||
}
|
||||
|
||||
class async_logger final : public std::enable_shared_from_this<async_logger>, public logger
|
||||
{
|
||||
friend class details::thread_pool;
|
||||
|
||||
public:
|
||||
template<typename It>
|
||||
async_logger(std::string logger_name, It begin, It end, std::weak_ptr<details::thread_pool> tp,
|
||||
async_overflow_policy overflow_policy = async_overflow_policy::block);
|
||||
|
||||
async_logger(std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp,
|
||||
async_overflow_policy overflow_policy = async_overflow_policy::block);
|
||||
|
||||
async_logger(std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp,
|
||||
async_overflow_policy overflow_policy = async_overflow_policy::block);
|
||||
|
||||
std::shared_ptr<logger> clone(std::string new_name) override;
|
||||
|
||||
protected:
|
||||
void sink_it_(details::log_msg &msg) override;
|
||||
void flush_() override;
|
||||
|
||||
void backend_log_(const details::log_msg &incoming_log_msg);
|
||||
void backend_flush_();
|
||||
|
||||
private:
|
||||
std::weak_ptr<details::thread_pool> thread_pool_;
|
||||
async_overflow_policy overflow_policy_;
|
||||
};
|
||||
} // namespace spdlog
|
||||
|
||||
#include "details/async_logger_impl.h"
|
243
third_party/spdlog/include/spdlog/common.h
vendored
Normal file
243
third_party/spdlog/include/spdlog/common.h
vendored
Normal file
@ -0,0 +1,243 @@
|
||||
//
|
||||
// Copyright(c) 2015 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "spdlog/tweakme.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#endif
|
||||
|
||||
#include "spdlog/details/null_mutex.h"
|
||||
|
||||
#include "spdlog/fmt/fmt.h"
|
||||
|
||||
// visual studio upto 2013 does not support noexcept nor constexpr
|
||||
#if defined(_MSC_VER) && (_MSC_VER < 1900)
|
||||
#define SPDLOG_NOEXCEPT throw()
|
||||
#define SPDLOG_CONSTEXPR
|
||||
#else
|
||||
#define SPDLOG_NOEXCEPT noexcept
|
||||
#define SPDLOG_CONSTEXPR constexpr
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define SPDLOG_DEPRECATED __attribute__((deprecated))
|
||||
#elif defined(_MSC_VER)
|
||||
#define SPDLOG_DEPRECATED __declspec(deprecated)
|
||||
#else
|
||||
#define SPDLOG_DEPRECATED
|
||||
#endif
|
||||
|
||||
// disable thread local on msvc 2013
|
||||
#ifndef SPDLOG_NO_TLS
|
||||
#if (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__cplusplus_winrt)
|
||||
#define SPDLOG_NO_TLS 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Get the basename of __FILE__ (at compile time if possible)
|
||||
#if FMT_HAS_FEATURE(__builtin_strrchr)
|
||||
#define SPDLOG_STRRCHR(str, sep) __builtin_strrchr(str, sep)
|
||||
#else
|
||||
#define SPDLOG_STRRCHR(str, sep) strrchr(str, sep)
|
||||
#endif //__builtin_strrchr not defined
|
||||
|
||||
#ifdef _WIN32
|
||||
#define SPDLOG_FILE_BASENAME(file) SPDLOG_STRRCHR("\\" file, '\\') + 1
|
||||
#else
|
||||
#define SPDLOG_FILE_BASENAME(file) SPDLOG_STRRCHR("/" file, '/') + 1
|
||||
#endif
|
||||
|
||||
#ifndef SPDLOG_FUNCTION
|
||||
#define SPDLOG_FUNCTION __FUNCTION__
|
||||
#endif
|
||||
|
||||
namespace spdlog {
|
||||
|
||||
class formatter;
|
||||
|
||||
namespace sinks {
|
||||
class sink;
|
||||
}
|
||||
|
||||
using log_clock = std::chrono::system_clock;
|
||||
using sink_ptr = std::shared_ptr<sinks::sink>;
|
||||
using sinks_init_list = std::initializer_list<sink_ptr>;
|
||||
using log_err_handler = std::function<void(const std::string &err_msg)>;
|
||||
|
||||
// string_view type - either std::string_view or fmt::string_view (pre c++17)
|
||||
#if defined(FMT_USE_STD_STRING_VIEW)
|
||||
using string_view_t = std::string_view;
|
||||
#else
|
||||
using string_view_t = fmt::string_view;
|
||||
#endif
|
||||
|
||||
#if defined(SPDLOG_NO_ATOMIC_LEVELS)
|
||||
using level_t = details::null_atomic_int;
|
||||
#else
|
||||
using level_t = std::atomic<int>;
|
||||
#endif
|
||||
|
||||
#define SPDLOG_LEVEL_TRACE 0
|
||||
#define SPDLOG_LEVEL_DEBUG 1
|
||||
#define SPDLOG_LEVEL_INFO 2
|
||||
#define SPDLOG_LEVEL_WARN 3
|
||||
#define SPDLOG_LEVEL_ERROR 4
|
||||
#define SPDLOG_LEVEL_CRITICAL 5
|
||||
#define SPDLOG_LEVEL_OFF 6
|
||||
|
||||
#if !defined(SPDLOG_ACTIVE_LEVEL)
|
||||
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
|
||||
#endif
|
||||
|
||||
// Log level enum
|
||||
namespace level {
|
||||
enum level_enum
|
||||
{
|
||||
trace = SPDLOG_LEVEL_TRACE,
|
||||
debug = SPDLOG_LEVEL_DEBUG,
|
||||
info = SPDLOG_LEVEL_INFO,
|
||||
warn = SPDLOG_LEVEL_WARN,
|
||||
err = SPDLOG_LEVEL_ERROR,
|
||||
critical = SPDLOG_LEVEL_CRITICAL,
|
||||
off = SPDLOG_LEVEL_OFF,
|
||||
};
|
||||
|
||||
#if !defined(SPDLOG_LEVEL_NAMES)
|
||||
#define SPDLOG_LEVEL_NAMES \
|
||||
{ \
|
||||
"trace", "debug", "info", "warning", "error", "critical", "off" \
|
||||
}
|
||||
#endif
|
||||
|
||||
static string_view_t level_string_views[] SPDLOG_LEVEL_NAMES;
|
||||
static const char *short_level_names[]{"T", "D", "I", "W", "E", "C", "O"};
|
||||
|
||||
inline string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT
|
||||
{
|
||||
return level_string_views[l];
|
||||
}
|
||||
|
||||
inline const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT
|
||||
{
|
||||
return short_level_names[l];
|
||||
}
|
||||
|
||||
inline spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT
|
||||
{
|
||||
int level = 0;
|
||||
for (const auto &level_str : level_string_views)
|
||||
{
|
||||
if (level_str == name)
|
||||
{
|
||||
return static_cast<level::level_enum>(level);
|
||||
}
|
||||
level++;
|
||||
}
|
||||
return level::off;
|
||||
}
|
||||
|
||||
using level_hasher = std::hash<int>;
|
||||
} // namespace level
|
||||
|
||||
//
|
||||
// Pattern time - specific time getting to use for pattern_formatter.
|
||||
// local time by default
|
||||
//
|
||||
enum class pattern_time_type
|
||||
{
|
||||
local, // log localtime
|
||||
utc // log utc
|
||||
};
|
||||
|
||||
//
|
||||
// Log exception
|
||||
//
|
||||
class spdlog_ex : public std::exception
|
||||
{
|
||||
public:
|
||||
explicit spdlog_ex(std::string msg)
|
||||
: msg_(std::move(msg))
|
||||
{
|
||||
}
|
||||
|
||||
spdlog_ex(const std::string &msg, int last_errno)
|
||||
{
|
||||
fmt::memory_buffer outbuf;
|
||||
fmt::format_system_error(outbuf, last_errno, msg);
|
||||
msg_ = fmt::to_string(outbuf);
|
||||
}
|
||||
|
||||
const char *what() const SPDLOG_NOEXCEPT override
|
||||
{
|
||||
return msg_.c_str();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
//
|
||||
// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined)
|
||||
//
|
||||
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
|
||||
using filename_t = std::wstring;
|
||||
#else
|
||||
using filename_t = std::string;
|
||||
#endif
|
||||
|
||||
struct source_loc
|
||||
{
|
||||
SPDLOG_CONSTEXPR source_loc()
|
||||
: filename{""}
|
||||
, line{0}
|
||||
, funcname{""}
|
||||
{
|
||||
}
|
||||
SPDLOG_CONSTEXPR source_loc(const char *filename, int line, const char *funcname)
|
||||
: filename{filename}
|
||||
, line{static_cast<uint32_t>(line)}
|
||||
, funcname{funcname}
|
||||
{
|
||||
}
|
||||
|
||||
SPDLOG_CONSTEXPR bool empty() const SPDLOG_NOEXCEPT
|
||||
{
|
||||
return line == 0;
|
||||
}
|
||||
const char *filename;
|
||||
uint32_t line;
|
||||
const char *funcname;
|
||||
};
|
||||
|
||||
namespace details {
|
||||
// make_unique support for pre c++14
|
||||
|
||||
#if __cplusplus >= 201402L // C++14 and beyond
|
||||
using std::make_unique;
|
||||
#else
|
||||
template<typename T, typename... Args>
|
||||
std::unique_ptr<T> make_unique(Args &&... args)
|
||||
{
|
||||
static_assert(!std::is_array<T>::value, "arrays not supported");
|
||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
#endif
|
||||
} // namespace details
|
||||
} // namespace spdlog
|
110
third_party/spdlog/include/spdlog/details/async_logger_impl.h
vendored
Normal file
110
third_party/spdlog/include/spdlog/details/async_logger_impl.h
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
//
|
||||
// Copyright(c) 2015 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
// async logger implementation
|
||||
// uses a thread pool to perform the actual logging
|
||||
|
||||
#include "spdlog/details/thread_pool.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
template<typename It>
|
||||
inline spdlog::async_logger::async_logger(
|
||||
std::string logger_name, It begin, It end, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy)
|
||||
: logger(std::move(logger_name), begin, end)
|
||||
, thread_pool_(std::move(tp))
|
||||
, overflow_policy_(overflow_policy)
|
||||
{
|
||||
}
|
||||
|
||||
inline spdlog::async_logger::async_logger(
|
||||
std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy)
|
||||
: async_logger(std::move(logger_name), sinks_list.begin(), sinks_list.end(), std::move(tp), overflow_policy)
|
||||
{
|
||||
}
|
||||
|
||||
inline spdlog::async_logger::async_logger(
|
||||
std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy)
|
||||
: async_logger(std::move(logger_name), {std::move(single_sink)}, std::move(tp), overflow_policy)
|
||||
{
|
||||
}
|
||||
|
||||
// send the log message to the thread pool
|
||||
inline void spdlog::async_logger::sink_it_(details::log_msg &msg)
|
||||
{
|
||||
#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER)
|
||||
incr_msg_counter_(msg);
|
||||
#endif
|
||||
if (auto pool_ptr = thread_pool_.lock())
|
||||
{
|
||||
pool_ptr->post_log(shared_from_this(), msg, overflow_policy_);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw spdlog_ex("async log: thread pool doesn't exist anymore");
|
||||
}
|
||||
}
|
||||
|
||||
// send flush request to the thread pool
|
||||
inline void spdlog::async_logger::flush_()
|
||||
{
|
||||
if (auto pool_ptr = thread_pool_.lock())
|
||||
{
|
||||
pool_ptr->post_flush(shared_from_this(), overflow_policy_);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw spdlog_ex("async flush: thread pool doesn't exist anymore");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// backend functions - called from the thread pool to do the actual job
|
||||
//
|
||||
inline void spdlog::async_logger::backend_log_(const details::log_msg &incoming_log_msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (auto &s : sinks_)
|
||||
{
|
||||
if (s->should_log(incoming_log_msg.level))
|
||||
{
|
||||
s->log(incoming_log_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
SPDLOG_CATCH_AND_HANDLE
|
||||
|
||||
if (should_flush_(incoming_log_msg))
|
||||
{
|
||||
backend_flush_();
|
||||
}
|
||||
}
|
||||
|
||||
inline void spdlog::async_logger::backend_flush_()
|
||||
{
|
||||
try
|
||||
{
|
||||
for (auto &sink : sinks_)
|
||||
{
|
||||
sink->flush();
|
||||
}
|
||||
}
|
||||
SPDLOG_CATCH_AND_HANDLE
|
||||
}
|
||||
|
||||
inline std::shared_ptr<spdlog::logger> spdlog::async_logger::clone(std::string new_name)
|
||||
{
|
||||
auto cloned = std::make_shared<spdlog::async_logger>(std::move(new_name), sinks_.begin(), sinks_.end(), thread_pool_, overflow_policy_);
|
||||
|
||||
cloned->set_level(this->level());
|
||||
cloned->flush_on(this->flush_level());
|
||||
cloned->set_error_handler(this->error_handler());
|
||||
return std::move(cloned);
|
||||
}
|
72
third_party/spdlog/include/spdlog/details/circular_q.h
vendored
Normal file
72
third_party/spdlog/include/spdlog/details/circular_q.h
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
//
|
||||
// Copyright(c) 2018 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
// cirucal q view of std::vector.
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace spdlog {
|
||||
namespace details {
|
||||
template<typename T>
|
||||
class circular_q
|
||||
{
|
||||
public:
|
||||
using item_type = T;
|
||||
|
||||
explicit circular_q(size_t max_items)
|
||||
: max_items_(max_items + 1) // one item is reserved as marker for full q
|
||||
, v_(max_items_)
|
||||
{
|
||||
}
|
||||
|
||||
// push back, overrun (oldest) item if no room left
|
||||
void push_back(T &&item)
|
||||
{
|
||||
v_[tail_] = std::move(item);
|
||||
tail_ = (tail_ + 1) % max_items_;
|
||||
|
||||
if (tail_ == head_) // overrun last item if full
|
||||
{
|
||||
head_ = (head_ + 1) % max_items_;
|
||||
++overrun_counter_;
|
||||
}
|
||||
}
|
||||
|
||||
// Pop item from front.
|
||||
// If there are no elements in the container, the behavior is undefined.
|
||||
void pop_front(T &popped_item)
|
||||
{
|
||||
popped_item = std::move(v_[head_]);
|
||||
head_ = (head_ + 1) % max_items_;
|
||||
}
|
||||
|
||||
bool empty()
|
||||
{
|
||||
return tail_ == head_;
|
||||
}
|
||||
|
||||
bool full()
|
||||
{
|
||||
// head is ahead of the tail by 1
|
||||
return ((tail_ + 1) % max_items_) == head_;
|
||||
}
|
||||
|
||||
size_t overrun_counter() const
|
||||
{
|
||||
return overrun_counter_;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t max_items_;
|
||||
typename std::vector<T>::size_type head_ = 0;
|
||||
typename std::vector<T>::size_type tail_ = 0;
|
||||
|
||||
std::vector<T> v_;
|
||||
|
||||
size_t overrun_counter_ = 0;
|
||||
};
|
||||
} // namespace details
|
||||
} // namespace spdlog
|
74
third_party/spdlog/include/spdlog/details/console_globals.h
vendored
Normal file
74
third_party/spdlog/include/spdlog/details/console_globals.h
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
//
|
||||
// Copyright(c) 2018 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
#include "spdlog/details/null_mutex.h"
|
||||
#include <cstdio>
|
||||
#include <mutex>
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX // prevent windows redefining min/max
|
||||
#endif
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace spdlog {
|
||||
namespace details {
|
||||
struct console_stdout
|
||||
{
|
||||
static std::FILE *stream()
|
||||
{
|
||||
return stdout;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
static HANDLE handle()
|
||||
{
|
||||
return ::GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
struct console_stderr
|
||||
{
|
||||
static std::FILE *stream()
|
||||
{
|
||||
return stderr;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
static HANDLE handle()
|
||||
{
|
||||
return ::GetStdHandle(STD_ERROR_HANDLE);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
struct console_mutex
|
||||
{
|
||||
using mutex_t = std::mutex;
|
||||
static mutex_t &mutex()
|
||||
{
|
||||
static mutex_t s_mutex;
|
||||
return s_mutex;
|
||||
}
|
||||
};
|
||||
|
||||
struct console_nullmutex
|
||||
{
|
||||
using mutex_t = null_mutex;
|
||||
static mutex_t &mutex()
|
||||
{
|
||||
static mutex_t s_mutex;
|
||||
return s_mutex;
|
||||
}
|
||||
};
|
||||
} // namespace details
|
||||
} // namespace spdlog
|
152
third_party/spdlog/include/spdlog/details/file_helper.h
vendored
Normal file
152
third_party/spdlog/include/spdlog/details/file_helper.h
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
//
|
||||
// Copyright(c) 2015 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
// Helper class for file sinks.
|
||||
// When failing to open a file, retry several times(5) with a delay interval(10 ms).
|
||||
// Throw spdlog_ex exception on errors.
|
||||
|
||||
#include "spdlog/details/log_msg.h"
|
||||
#include "spdlog/details/os.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
|
||||
namespace spdlog {
|
||||
namespace details {
|
||||
|
||||
class file_helper
|
||||
{
|
||||
|
||||
public:
|
||||
const int open_tries = 5;
|
||||
const int open_interval = 10;
|
||||
|
||||
explicit file_helper() = default;
|
||||
|
||||
file_helper(const file_helper &) = delete;
|
||||
file_helper &operator=(const file_helper &) = delete;
|
||||
|
||||
~file_helper()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void open(const filename_t &fname, bool truncate = false)
|
||||
{
|
||||
close();
|
||||
auto *mode = truncate ? SPDLOG_FILENAME_T("wb") : SPDLOG_FILENAME_T("ab");
|
||||
_filename = fname;
|
||||
for (int tries = 0; tries < open_tries; ++tries)
|
||||
{
|
||||
if (!os::fopen_s(&fd_, fname, mode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
details::os::sleep_for_millis(open_interval);
|
||||
}
|
||||
|
||||
throw spdlog_ex("Failed opening file " + os::filename_to_str(_filename) + " for writing", errno);
|
||||
}
|
||||
|
||||
void reopen(bool truncate)
|
||||
{
|
||||
if (_filename.empty())
|
||||
{
|
||||
throw spdlog_ex("Failed re opening file - was not opened before");
|
||||
}
|
||||
open(_filename, truncate);
|
||||
}
|
||||
|
||||
void flush()
|
||||
{
|
||||
std::fflush(fd_);
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
if (fd_ != nullptr)
|
||||
{
|
||||
std::fclose(fd_);
|
||||
fd_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void write(const fmt::memory_buffer &buf)
|
||||
{
|
||||
size_t msg_size = buf.size();
|
||||
auto data = buf.data();
|
||||
if (std::fwrite(data, 1, msg_size, fd_) != msg_size)
|
||||
{
|
||||
throw spdlog_ex("Failed writing to file " + os::filename_to_str(_filename), errno);
|
||||
}
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
if (fd_ == nullptr)
|
||||
{
|
||||
throw spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(_filename));
|
||||
}
|
||||
return os::filesize(fd_);
|
||||
}
|
||||
|
||||
const filename_t &filename() const
|
||||
{
|
||||
return _filename;
|
||||
}
|
||||
|
||||
static bool file_exists(const filename_t &fname)
|
||||
{
|
||||
return os::file_exists(fname);
|
||||
}
|
||||
|
||||
//
|
||||
// return file path and its extension:
|
||||
//
|
||||
// "mylog.txt" => ("mylog", ".txt")
|
||||
// "mylog" => ("mylog", "")
|
||||
// "mylog." => ("mylog.", "")
|
||||
// "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt")
|
||||
//
|
||||
// the starting dot in filenames is ignored (hidden files):
|
||||
//
|
||||
// ".mylog" => (".mylog". "")
|
||||
// "my_folder/.mylog" => ("my_folder/.mylog", "")
|
||||
// "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt")
|
||||
static std::tuple<filename_t, filename_t> split_by_extension(const spdlog::filename_t &fname)
|
||||
{
|
||||
auto ext_index = fname.rfind('.');
|
||||
|
||||
// no valid extension found - return whole path and empty string as
|
||||
// extension
|
||||
if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1)
|
||||
{
|
||||
return std::make_tuple(fname, spdlog::filename_t());
|
||||
}
|
||||
|
||||
// treat casese like "/etc/rc.d/somelogfile or "/abc/.hiddenfile"
|
||||
auto folder_index = fname.rfind(details::os::folder_sep);
|
||||
if (folder_index != filename_t::npos && folder_index >= ext_index - 1)
|
||||
{
|
||||
return std::make_tuple(fname, spdlog::filename_t());
|
||||
}
|
||||
|
||||
// finally - return a valid base and extension tuple
|
||||
return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index));
|
||||
}
|
||||
|
||||
private:
|
||||
std::FILE *fd_{nullptr};
|
||||
filename_t _filename;
|
||||
};
|
||||
} // namespace details
|
||||
} // namespace spdlog
|
122
third_party/spdlog/include/spdlog/details/fmt_helper.h
vendored
Normal file
122
third_party/spdlog/include/spdlog/details/fmt_helper.h
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
//
|
||||
// Created by gabi on 6/15/18.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <type_traits>
|
||||
#include "spdlog/fmt/fmt.h"
|
||||
|
||||
// Some fmt helpers to efficiently format and pad ints and strings
|
||||
namespace spdlog {
|
||||
namespace details {
|
||||
namespace fmt_helper {
|
||||
|
||||
template<size_t Buffer_Size>
|
||||
inline spdlog::string_view_t to_string_view(const fmt::basic_memory_buffer<char, Buffer_Size> &buf) SPDLOG_NOEXCEPT
|
||||
{
|
||||
return spdlog::string_view_t(buf.data(), buf.size());
|
||||
}
|
||||
|
||||
template<size_t Buffer_Size1, size_t Buffer_Size2>
|
||||
inline void append_buf(const fmt::basic_memory_buffer<char, Buffer_Size1> &buf, fmt::basic_memory_buffer<char, Buffer_Size2> &dest)
|
||||
{
|
||||
auto *buf_ptr = buf.data();
|
||||
dest.append(buf_ptr, buf_ptr + buf.size());
|
||||
}
|
||||
|
||||
template<size_t Buffer_Size>
|
||||
inline void append_string_view(spdlog::string_view_t view, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
|
||||
{
|
||||
auto *buf_ptr = view.data();
|
||||
if (buf_ptr != nullptr)
|
||||
{
|
||||
dest.append(buf_ptr, buf_ptr + view.size());
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, size_t Buffer_Size>
|
||||
inline void append_int(T n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
|
||||
{
|
||||
fmt::format_int i(n);
|
||||
dest.append(i.data(), i.data() + i.size());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline unsigned count_digits(T n)
|
||||
{
|
||||
using count_type = typename std::conditional<(sizeof(T) > sizeof(uint32_t)), uint64_t, uint32_t>::type;
|
||||
return static_cast<unsigned>(fmt::internal::count_digits(static_cast<count_type>(n)));
|
||||
}
|
||||
|
||||
template<size_t Buffer_Size>
|
||||
inline void pad2(int n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
|
||||
{
|
||||
if (n > 99)
|
||||
{
|
||||
append_int(n, dest);
|
||||
}
|
||||
else if (n > 9) // 10-99
|
||||
{
|
||||
dest.push_back(static_cast<char>('0' + n / 10));
|
||||
dest.push_back(static_cast<char>('0' + n % 10));
|
||||
}
|
||||
else if (n >= 0) // 0-9
|
||||
{
|
||||
dest.push_back('0');
|
||||
dest.push_back(static_cast<char>('0' + n));
|
||||
}
|
||||
else // negatives (unlikely, but just in case, let fmt deal with it)
|
||||
{
|
||||
fmt::format_to(dest, "{:02}", n);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, size_t Buffer_Size>
|
||||
inline void pad_uint(T n, unsigned int width, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
|
||||
{
|
||||
static_assert(std::is_unsigned<T>::value, "pad_uint must get unsigned T");
|
||||
auto digits = count_digits(n);
|
||||
if (width > digits)
|
||||
{
|
||||
const char *zeroes = "0000000000000000000";
|
||||
dest.append(zeroes, zeroes + width - digits);
|
||||
}
|
||||
append_int(n, dest);
|
||||
}
|
||||
|
||||
template<typename T, size_t Buffer_Size>
|
||||
inline void pad3(T n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
|
||||
{
|
||||
pad_uint(n, 3, dest);
|
||||
}
|
||||
|
||||
template<typename T, size_t Buffer_Size>
|
||||
inline void pad6(T n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
|
||||
{
|
||||
pad_uint(n, 6, dest);
|
||||
}
|
||||
|
||||
template<typename T, size_t Buffer_Size>
|
||||
inline void pad9(T n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
|
||||
{
|
||||
pad_uint(n, 9, dest);
|
||||
}
|
||||
|
||||
// return fraction of a second of the given time_point.
|
||||
// e.g.
|
||||
// fraction<std::milliseconds>(tp) -> will return the millis part of the second
|
||||
template<typename ToDuration>
|
||||
inline ToDuration time_fraction(const log_clock::time_point &tp)
|
||||
{
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::seconds;
|
||||
auto duration = tp.time_since_epoch();
|
||||
auto secs = duration_cast<seconds>(duration);
|
||||
return duration_cast<ToDuration>(duration) - duration_cast<ToDuration>(secs);
|
||||
}
|
||||
|
||||
} // namespace fmt_helper
|
||||
} // namespace details
|
||||
} // namespace spdlog
|
55
third_party/spdlog/include/spdlog/details/log_msg.h
vendored
Normal file
55
third_party/spdlog/include/spdlog/details/log_msg.h
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
//
|
||||
// Copyright(c) 2015 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "spdlog/common.h"
|
||||
#include "spdlog/details/os.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace spdlog {
|
||||
namespace details {
|
||||
struct log_msg
|
||||
{
|
||||
|
||||
log_msg(source_loc loc, const std::string *loggers_name, level::level_enum lvl, string_view_t view)
|
||||
: logger_name(loggers_name)
|
||||
, level(lvl)
|
||||
#ifndef SPDLOG_NO_DATETIME
|
||||
, time(os::now())
|
||||
#endif
|
||||
|
||||
#ifndef SPDLOG_NO_THREAD_ID
|
||||
, thread_id(os::thread_id())
|
||||
#endif
|
||||
, source(loc)
|
||||
, payload(view)
|
||||
{
|
||||
}
|
||||
|
||||
log_msg(const std::string *loggers_name, level::level_enum lvl, string_view_t view)
|
||||
: log_msg(source_loc{}, loggers_name, lvl, view)
|
||||
{
|
||||
}
|
||||
|
||||
log_msg(const log_msg &other) = default;
|
||||
|
||||
const std::string *logger_name{nullptr};
|
||||
level::level_enum level{level::off};
|
||||
log_clock::time_point time;
|
||||
size_t thread_id{0};
|
||||
size_t msg_id{0};
|
||||
|
||||
// wrapping the formatted text with color (updated by pattern_formatter).
|
||||
mutable size_t color_range_start{0};
|
||||
mutable size_t color_range_end{0};
|
||||
|
||||
source_loc source;
|
||||
const string_view_t payload;
|
||||
};
|
||||
} // namespace details
|
||||
} // namespace spdlog
|
441
third_party/spdlog/include/spdlog/details/logger_impl.h
vendored
Normal file
441
third_party/spdlog/include/spdlog/details/logger_impl.h
vendored
Normal file
@ -0,0 +1,441 @@
|
||||
//
|
||||
// Copyright(c) 2015 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "spdlog/details/fmt_helper.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#define SPDLOG_CATCH_AND_HANDLE \
|
||||
catch (const std::exception &ex) \
|
||||
{ \
|
||||
err_handler_(ex.what()); \
|
||||
} \
|
||||
catch (...) \
|
||||
{ \
|
||||
err_handler_("Unknown exception in logger"); \
|
||||
}
|
||||
|
||||
// create logger with given name, sinks and the default pattern formatter
|
||||
// all other ctors will call this one
|
||||
template<typename It>
|
||||
inline spdlog::logger::logger(std::string logger_name, It begin, It end)
|
||||
: name_(std::move(logger_name))
|
||||
, sinks_(begin, end)
|
||||
{
|
||||
}
|
||||
|
||||
// ctor with sinks as init list
|
||||
inline spdlog::logger::logger(std::string logger_name, sinks_init_list sinks_list)
|
||||
: logger(std::move(logger_name), sinks_list.begin(), sinks_list.end())
|
||||
{
|
||||
}
|
||||
|
||||
// ctor with single sink
|
||||
inline spdlog::logger::logger(std::string logger_name, spdlog::sink_ptr single_sink)
|
||||
: logger(std::move(logger_name), {std::move(single_sink)})
|
||||
{
|
||||
}
|
||||
|
||||
inline spdlog::logger::~logger() = default;
|
||||
|
||||
inline void spdlog::logger::set_formatter(std::unique_ptr<spdlog::formatter> f)
|
||||
{
|
||||
for (auto &sink : sinks_)
|
||||
{
|
||||
sink->set_formatter(f->clone());
|
||||
}
|
||||
}
|
||||
|
||||
inline void spdlog::logger::set_pattern(std::string pattern, pattern_time_type time_type)
|
||||
{
|
||||
auto new_formatter = details::make_unique<spdlog::pattern_formatter>(std::move(pattern), time_type);
|
||||
set_formatter(std::move(new_formatter));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::log(source_loc source, level::level_enum lvl, const char *fmt, const Args &... args)
|
||||
{
|
||||
if (!should_log(lvl))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using details::fmt_helper::to_string_view;
|
||||
fmt::memory_buffer buf;
|
||||
fmt::format_to(buf, fmt, args...);
|
||||
details::log_msg log_msg(source, &name_, lvl, to_string_view(buf));
|
||||
sink_it_(log_msg);
|
||||
}
|
||||
SPDLOG_CATCH_AND_HANDLE
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::log(level::level_enum lvl, const char *fmt, const Args &... args)
|
||||
{
|
||||
log(source_loc{}, lvl, fmt, args...);
|
||||
}
|
||||
|
||||
inline void spdlog::logger::log(source_loc source, level::level_enum lvl, const char *msg)
|
||||
{
|
||||
if (!should_log(lvl))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
details::log_msg log_msg(source, &name_, lvl, spdlog::string_view_t(msg));
|
||||
sink_it_(log_msg);
|
||||
}
|
||||
SPDLOG_CATCH_AND_HANDLE
|
||||
}
|
||||
|
||||
inline void spdlog::logger::log(level::level_enum lvl, const char *msg)
|
||||
{
|
||||
log(source_loc{}, lvl, msg);
|
||||
}
|
||||
|
||||
template<class T, typename std::enable_if<std::is_convertible<T, spdlog::string_view_t>::value, T>::type *>
|
||||
inline void spdlog::logger::log(source_loc source, level::level_enum lvl, const T &msg)
|
||||
{
|
||||
if (!should_log(lvl))
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
details::log_msg log_msg(source, &name_, lvl, msg);
|
||||
sink_it_(log_msg);
|
||||
}
|
||||
SPDLOG_CATCH_AND_HANDLE
|
||||
}
|
||||
|
||||
template<class T, typename std::enable_if<std::is_convertible<T, spdlog::string_view_t>::value, T>::type *>
|
||||
inline void spdlog::logger::log(level::level_enum lvl, const T &msg)
|
||||
{
|
||||
log(source_loc{}, lvl, msg);
|
||||
}
|
||||
|
||||
template<class T, typename std::enable_if<!std::is_convertible<T, spdlog::string_view_t>::value, T>::type *>
|
||||
inline void spdlog::logger::log(source_loc source, level::level_enum lvl, const T &msg)
|
||||
{
|
||||
if (!should_log(lvl))
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
using details::fmt_helper::to_string_view;
|
||||
fmt::memory_buffer buf;
|
||||
fmt::format_to(buf, "{}", msg);
|
||||
details::log_msg log_msg(source, &name_, lvl, to_string_view(buf));
|
||||
sink_it_(log_msg);
|
||||
}
|
||||
SPDLOG_CATCH_AND_HANDLE
|
||||
}
|
||||
|
||||
template<class T, typename std::enable_if<!std::is_convertible<T, spdlog::string_view_t>::value, T>::type *>
|
||||
inline void spdlog::logger::log(level::level_enum lvl, const T &msg)
|
||||
{
|
||||
log(source_loc{}, lvl, msg);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::trace(const char *fmt, const Args &... args)
|
||||
{
|
||||
log(level::trace, fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::debug(const char *fmt, const Args &... args)
|
||||
{
|
||||
log(level::debug, fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::info(const char *fmt, const Args &... args)
|
||||
{
|
||||
log(level::info, fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::warn(const char *fmt, const Args &... args)
|
||||
{
|
||||
log(level::warn, fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::error(const char *fmt, const Args &... args)
|
||||
{
|
||||
log(level::err, fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::critical(const char *fmt, const Args &... args)
|
||||
{
|
||||
log(level::critical, fmt, args...);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void spdlog::logger::trace(const T &msg)
|
||||
{
|
||||
log(level::trace, msg);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void spdlog::logger::debug(const T &msg)
|
||||
{
|
||||
log(level::debug, msg);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void spdlog::logger::info(const T &msg)
|
||||
{
|
||||
log(level::info, msg);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void spdlog::logger::warn(const T &msg)
|
||||
{
|
||||
log(level::warn, msg);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void spdlog::logger::error(const T &msg)
|
||||
{
|
||||
log(level::err, msg);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void spdlog::logger::critical(const T &msg)
|
||||
{
|
||||
log(level::critical, msg);
|
||||
}
|
||||
|
||||
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
|
||||
|
||||
inline void wbuf_to_utf8buf(const fmt::wmemory_buffer &wbuf, fmt::memory_buffer &target)
|
||||
{
|
||||
int wbuf_size = static_cast<int>(wbuf.size());
|
||||
if (wbuf_size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto result_size = ::WideCharToMultiByte(CP_UTF8, 0, wbuf.data(), wbuf_size, NULL, 0, NULL, NULL);
|
||||
|
||||
if (result_size > 0)
|
||||
{
|
||||
target.resize(result_size);
|
||||
::WideCharToMultiByte(CP_UTF8, 0, wbuf.data(), wbuf_size, &target.data()[0], result_size, NULL, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw spdlog::spdlog_ex(fmt::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::log(source_loc source, level::level_enum lvl, const wchar_t *fmt, const Args &... args)
|
||||
{
|
||||
if (!should_log(lvl))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// format to wmemory_buffer and convert to utf8
|
||||
using details::fmt_helper::to_string_view;
|
||||
fmt::wmemory_buffer wbuf;
|
||||
fmt::format_to(wbuf, fmt, args...);
|
||||
fmt::memory_buffer buf;
|
||||
wbuf_to_utf8buf(wbuf, buf);
|
||||
details::log_msg log_msg(source, &name_, lvl, to_string_view(buf));
|
||||
sink_it_(log_msg);
|
||||
}
|
||||
SPDLOG_CATCH_AND_HANDLE
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::log(level::level_enum lvl, const wchar_t *fmt, const Args &... args)
|
||||
{
|
||||
log(source_loc{}, lvl, fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::trace(const wchar_t *fmt, const Args &... args)
|
||||
{
|
||||
log(level::trace, fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::debug(const wchar_t *fmt, const Args &... args)
|
||||
{
|
||||
log(level::debug, fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::info(const wchar_t *fmt, const Args &... args)
|
||||
{
|
||||
log(level::info, fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::warn(const wchar_t *fmt, const Args &... args)
|
||||
{
|
||||
log(level::warn, fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::error(const wchar_t *fmt, const Args &... args)
|
||||
{
|
||||
log(level::err, fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void spdlog::logger::critical(const wchar_t *fmt, const Args &... args)
|
||||
{
|
||||
log(level::critical, fmt, args...);
|
||||
}
|
||||
|
||||
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT
|
||||
|
||||
//
|
||||
// name and level
|
||||
//
|
||||
inline const std::string &spdlog::logger::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
inline void spdlog::logger::set_level(spdlog::level::level_enum log_level)
|
||||
{
|
||||
level_.store(log_level);
|
||||
}
|
||||
|
||||
inline void spdlog::logger::set_error_handler(spdlog::log_err_handler err_handler)
|
||||
{
|
||||
err_handler_ = std::move(err_handler);
|
||||
}
|
||||
|
||||
inline spdlog::log_err_handler spdlog::logger::error_handler() const
|
||||
{
|
||||
return err_handler_;
|
||||
}
|
||||
|
||||
inline void spdlog::logger::flush()
|
||||
{
|
||||
try
|
||||
{
|
||||
flush_();
|
||||
}
|
||||
SPDLOG_CATCH_AND_HANDLE
|
||||
}
|
||||
|
||||
inline void spdlog::logger::flush_on(level::level_enum log_level)
|
||||
{
|
||||
flush_level_.store(log_level);
|
||||
}
|
||||
|
||||
inline spdlog::level::level_enum spdlog::logger::flush_level() const
|
||||
{
|
||||
return static_cast<spdlog::level::level_enum>(flush_level_.load(std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
inline bool spdlog::logger::should_flush_(const details::log_msg &msg)
|
||||
{
|
||||
auto flush_level = flush_level_.load(std::memory_order_relaxed);
|
||||
return (msg.level >= flush_level) && (msg.level != level::off);
|
||||
}
|
||||
|
||||
inline spdlog::level::level_enum spdlog::logger::default_level()
|
||||
{
|
||||
return static_cast<spdlog::level::level_enum>(SPDLOG_ACTIVE_LEVEL);
|
||||
}
|
||||
|
||||
inline spdlog::level::level_enum spdlog::logger::level() const
|
||||
{
|
||||
return static_cast<spdlog::level::level_enum>(level_.load(std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
inline bool spdlog::logger::should_log(spdlog::level::level_enum msg_level) const
|
||||
{
|
||||
return msg_level >= level_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
//
|
||||
// protected virtual called at end of each user log call (if enabled) by the
|
||||
// line_logger
|
||||
//
|
||||
inline void spdlog::logger::sink_it_(details::log_msg &msg)
|
||||
{
|
||||
#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER)
|
||||
incr_msg_counter_(msg);
|
||||
#endif
|
||||
for (auto &sink : sinks_)
|
||||
{
|
||||
if (sink->should_log(msg.level))
|
||||
{
|
||||
sink->log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (should_flush_(msg))
|
||||
{
|
||||
flush_();
|
||||
}
|
||||
}
|
||||
|
||||
inline void spdlog::logger::flush_()
|
||||
{
|
||||
for (auto &sink : sinks_)
|
||||
{
|
||||
sink->flush();
|
||||
}
|
||||
}
|
||||
|
||||
inline void spdlog::logger::default_err_handler_(const std::string &msg)
|
||||
{
|
||||
auto now = time(nullptr);
|
||||
if (now - last_err_time_ < 60)
|
||||
{
|
||||
return;
|
||||
}
|
||||
last_err_time_ = now;
|
||||
auto tm_time = details::os::localtime(now);
|
||||
char date_buf[100];
|
||||
std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time);
|
||||
fmt::print(stderr, "[*** LOG ERROR ***] [{}] [{}] {}\n", date_buf, name(), msg);
|
||||
}
|
||||
|
||||
inline void spdlog::logger::incr_msg_counter_(details::log_msg &msg)
|
||||
{
|
||||
msg.msg_id = msg_counter_.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
inline const std::vector<spdlog::sink_ptr> &spdlog::logger::sinks() const
|
||||
{
|
||||
return sinks_;
|
||||
}
|
||||
|
||||
inline std::vector<spdlog::sink_ptr> &spdlog::logger::sinks()
|
||||
{
|
||||
return sinks_;
|
||||
}
|
||||
|
||||
inline std::shared_ptr<spdlog::logger> spdlog::logger::clone(std::string logger_name)
|
||||
{
|
||||
auto cloned = std::make_shared<spdlog::logger>(std::move(logger_name), sinks_.begin(), sinks_.end());
|
||||
cloned->set_level(this->level());
|
||||
cloned->flush_on(this->flush_level());
|
||||
cloned->set_error_handler(this->error_handler());
|
||||
return cloned;
|
||||
}
|
121
third_party/spdlog/include/spdlog/details/mpmc_blocking_q.h
vendored
Normal file
121
third_party/spdlog/include/spdlog/details/mpmc_blocking_q.h
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// Copyright(c) 2018 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
// multi producer-multi consumer blocking queue.
|
||||
// enqueue(..) - will block until room found to put the new message.
|
||||
// enqueue_nowait(..) - will return immediately with false if no room left in
|
||||
// the queue.
|
||||
// dequeue_for(..) - will block until the queue is not empty or timeout have
|
||||
// passed.
|
||||
|
||||
#include "spdlog/details/circular_q.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
namespace spdlog {
|
||||
namespace details {
|
||||
|
||||
template<typename T>
|
||||
class mpmc_blocking_queue
|
||||
{
|
||||
public:
|
||||
using item_type = T;
|
||||
explicit mpmc_blocking_queue(size_t max_items)
|
||||
: q_(max_items)
|
||||
{
|
||||
}
|
||||
|
||||
#ifndef __MINGW32__
|
||||
// try to enqueue and block if no room left
|
||||
void enqueue(T &&item)
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queue_mutex_);
|
||||
pop_cv_.wait(lock, [this] { return !this->q_.full(); });
|
||||
q_.push_back(std::move(item));
|
||||
}
|
||||
push_cv_.notify_one();
|
||||
}
|
||||
|
||||
// enqueue immediately. overrun oldest message in the queue if no room left.
|
||||
void enqueue_nowait(T &&item)
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queue_mutex_);
|
||||
q_.push_back(std::move(item));
|
||||
}
|
||||
push_cv_.notify_one();
|
||||
}
|
||||
|
||||
// try to dequeue item. if no item found. wait upto timeout and try again
|
||||
// Return true, if succeeded dequeue item, false otherwise
|
||||
bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration)
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queue_mutex_);
|
||||
if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); }))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
q_.pop_front(popped_item);
|
||||
}
|
||||
pop_cv_.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
// apparently mingw deadlocks if the mutex is released before cv.notify_one(),
|
||||
// so release the mutex at the very end each function.
|
||||
|
||||
// try to enqueue and block if no room left
|
||||
void enqueue(T &&item)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queue_mutex_);
|
||||
pop_cv_.wait(lock, [this] { return !this->q_.full(); });
|
||||
q_.push_back(std::move(item));
|
||||
push_cv_.notify_one();
|
||||
}
|
||||
|
||||
// enqueue immediately. overrun oldest message in the queue if no room left.
|
||||
void enqueue_nowait(T &&item)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queue_mutex_);
|
||||
q_.push_back(std::move(item));
|
||||
push_cv_.notify_one();
|
||||
}
|
||||
|
||||
// try to dequeue item. if no item found. wait upto timeout and try again
|
||||
// Return true, if succeeded dequeue item, false otherwise
|
||||
bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queue_mutex_);
|
||||
if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); }))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
q_.pop_front(popped_item);
|
||||
pop_cv_.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
size_t overrun_counter()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queue_mutex_);
|
||||
return q_.overrun_counter();
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex queue_mutex_;
|
||||
std::condition_variable push_cv_;
|
||||
std::condition_variable pop_cv_;
|
||||
spdlog::details::circular_q<T> q_;
|
||||
};
|
||||
} // namespace details
|
||||
} // namespace spdlog
|
45
third_party/spdlog/include/spdlog/details/null_mutex.h
vendored
Normal file
45
third_party/spdlog/include/spdlog/details/null_mutex.h
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
//
|
||||
// Copyright(c) 2015 Gabi Melman.
|
||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
// null, no cost dummy "mutex" and dummy "atomic" int
|
||||
|
||||
namespace spdlog {
|
||||
namespace details {
|
||||
struct null_mutex
|
||||
{
|
||||
void lock() {}
|
||||
void unlock() {}
|
||||
bool try_lock()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct null_atomic_int
|
||||
{
|
||||
int value;
|
||||
null_atomic_int() = default;
|
||||
|
||||
explicit null_atomic_int(int val)
|
||||
: value(val)
|
||||
{
|
||||
}
|
||||
|
||||
int load(std::memory_order) const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
void store(int val)
|
||||
{
|
||||
value = val;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
} // namespace spdlog
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user