move sentry code around and add a stub unittest for it
This commit is contained in:
24
ixsentry/CMakeLists.txt
Normal file
24
ixsentry/CMakeLists.txt
Normal file
@ -0,0 +1,24 @@
|
||||
#
|
||||
# Author: Benjamin Sergeant
|
||||
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
#
|
||||
|
||||
set (IXSENTRY_SOURCES
|
||||
ixsentry/IXSentryClient.cpp
|
||||
)
|
||||
|
||||
set (IXSENTRY_HEADERS
|
||||
ixsentry/IXSentryClient.h
|
||||
)
|
||||
|
||||
add_library(ixsentry STATIC
|
||||
${IXSENTRY_SOURCES}
|
||||
${IXSENTRY_HEADERS}
|
||||
)
|
||||
|
||||
set(IXSENTRY_INCLUDE_DIRS
|
||||
.
|
||||
../third_party
|
||||
../third_party/spdlog/include)
|
||||
|
||||
target_include_directories( ixsentry PUBLIC ${IXSENTRY_INCLUDE_DIRS} )
|
207
ixsentry/ixsentry/IXSentryClient.cpp
Normal file
207
ixsentry/ixsentry/IXSentryClient.cpp
Normal file
@ -0,0 +1,207 @@
|
||||
/*
|
||||
* IXSentryClient.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXSentryClient.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
|
||||
namespace ix
|
||||
{
|
||||
SentryClient::SentryClient(const std::string& dsn)
|
||||
: _dsn(dsn)
|
||||
, _validDsn(false)
|
||||
, _luaFrameRegex("\t([^/]+):([0-9]+): in function '([^/]+)'")
|
||||
{
|
||||
const std::regex dsnRegex("(http[s]?)://([^:]+):([^@]+)@([^/]+)/([0-9]+)");
|
||||
std::smatch group;
|
||||
|
||||
if (std::regex_match(dsn, group, dsnRegex) && group.size() == 6)
|
||||
{
|
||||
_validDsn = true;
|
||||
|
||||
const auto scheme = group.str(1);
|
||||
const auto host = group.str(4);
|
||||
const auto project_id = group.str(5);
|
||||
_url = scheme + "://" + host + "/api/" + project_id + "/store/";
|
||||
|
||||
_publicKey = group.str(2);
|
||||
_secretKey = group.str(3);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t SentryClient::getTimestamp()
|
||||
{
|
||||
const auto tp = std::chrono::system_clock::now();
|
||||
const auto dur = tp.time_since_epoch();
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(dur).count();
|
||||
}
|
||||
|
||||
std::string SentryClient::getIso8601()
|
||||
{
|
||||
std::time_t now;
|
||||
std::time(&now);
|
||||
char buf[sizeof("2011-10-08T07:07:09Z")];
|
||||
std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", std::gmtime(&now));
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string SentryClient::computeAuthHeader()
|
||||
{
|
||||
std::string securityHeader("Sentry sentry_version=5");
|
||||
securityHeader += ",sentry_client=ws/1.0.0";
|
||||
securityHeader += ",sentry_timestamp=" + std::to_string(SentryClient::getTimestamp());
|
||||
securityHeader += ",sentry_key=" + _publicKey;
|
||||
securityHeader += ",sentry_secret=" + _secretKey;
|
||||
|
||||
return securityHeader;
|
||||
}
|
||||
|
||||
Json::Value SentryClient::parseLuaStackTrace(const std::string& stack)
|
||||
{
|
||||
Json::Value frames;
|
||||
|
||||
// Split by lines
|
||||
std::string line;
|
||||
std::stringstream tokenStream(stack);
|
||||
|
||||
std::smatch group;
|
||||
|
||||
while (std::getline(tokenStream, line))
|
||||
{
|
||||
// MapScene.lua:2169: in function 'singleCB'
|
||||
if (std::regex_match(line, group, _luaFrameRegex))
|
||||
{
|
||||
const auto fileName = group.str(1);
|
||||
const auto linenoStr = group.str(2);
|
||||
const auto function = group.str(3);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << linenoStr;
|
||||
uint64_t lineno;
|
||||
ss >> lineno;
|
||||
|
||||
Json::Value frame;
|
||||
frame["lineno"] = Json::UInt64(lineno);
|
||||
frame["filename"] = fileName;
|
||||
frame["function"] = function;
|
||||
|
||||
frames.append(frame);
|
||||
}
|
||||
}
|
||||
|
||||
std::reverse(frames.begin(), frames.end());
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
std::string parseExceptionName(const std::string& stack)
|
||||
{
|
||||
// Split by lines
|
||||
std::string line;
|
||||
std::stringstream tokenStream(stack);
|
||||
|
||||
// Extract the first line
|
||||
std::getline(tokenStream, line);
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
std::string SentryClient::computePayload(const Json::Value& msg)
|
||||
{
|
||||
Json::Value payload;
|
||||
|
||||
payload["platform"] = "python";
|
||||
payload["sdk"]["name"] = "ws";
|
||||
payload["sdk"]["version"] = "1.0.0";
|
||||
payload["timestamp"] = SentryClient::getIso8601();
|
||||
|
||||
bool isNoisyTypes = msg["id"].asString() == "game_noisytypes_id";
|
||||
|
||||
std::string stackTraceFieldName = isNoisyTypes ? "traceback" : "stack";
|
||||
std::string stack(msg["data"][stackTraceFieldName].asString());
|
||||
|
||||
Json::Value exception;
|
||||
exception["stacktrace"]["frames"] = parseLuaStackTrace(stack);
|
||||
exception["value"] = isNoisyTypes ? parseExceptionName(stack) : msg["data"]["message"];
|
||||
|
||||
payload["exception"].append(exception);
|
||||
|
||||
Json::Value extra;
|
||||
extra["cobra_event"] = msg;
|
||||
extra["cobra_event"] = msg;
|
||||
|
||||
//
|
||||
// "tags": [
|
||||
// [
|
||||
// "a",
|
||||
// "b"
|
||||
// ],
|
||||
// ]
|
||||
//
|
||||
Json::Value tags;
|
||||
|
||||
Json::Value gameTag;
|
||||
gameTag.append("game");
|
||||
gameTag.append(msg["device"]["game"]);
|
||||
tags.append(gameTag);
|
||||
|
||||
Json::Value userIdTag;
|
||||
userIdTag.append("userid");
|
||||
userIdTag.append(msg["device"]["user_id"]);
|
||||
tags.append(userIdTag);
|
||||
|
||||
Json::Value environmentTag;
|
||||
environmentTag.append("environment");
|
||||
environmentTag.append(msg["device"]["environment"]);
|
||||
tags.append(environmentTag);
|
||||
|
||||
payload["tags"] = tags;
|
||||
|
||||
return _jsonWriter.write(payload);
|
||||
}
|
||||
|
||||
std::pair<HttpResponsePtr, std::string> SentryClient::send(const Json::Value& msg, bool verbose)
|
||||
{
|
||||
auto args = _httpClient.createRequest();
|
||||
args->extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader();
|
||||
args->connectTimeout = 60;
|
||||
args->transferTimeout = 5 * 60;
|
||||
args->followRedirects = true;
|
||||
args->verbose = verbose;
|
||||
args->logger = [](const std::string& msg) { spdlog::info("request logger: {}", msg); };
|
||||
|
||||
std::string body = computePayload(msg);
|
||||
HttpResponsePtr response = _httpClient.post(_url, body, args);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
for (auto it : response->headers)
|
||||
{
|
||||
spdlog::info("{}: {}", it.first, it.second);
|
||||
}
|
||||
|
||||
spdlog::info("Upload size: {}", response->uploadSize);
|
||||
spdlog::info("Download size: {}", response->downloadSize);
|
||||
|
||||
spdlog::info("Status: {}", response->statusCode);
|
||||
if (response->errorCode != HttpErrorCode::Ok)
|
||||
{
|
||||
spdlog::info("error message: {}", response->errorMsg);
|
||||
}
|
||||
|
||||
if (response->headers["Content-Type"] != "application/octet-stream")
|
||||
{
|
||||
spdlog::info("payload: {}", response->payload);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(response, body);
|
||||
}
|
||||
} // namespace ix
|
47
ixsentry/ixsentry/IXSentryClient.h
Normal file
47
ixsentry/ixsentry/IXSentryClient.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* IXSentryClient.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <ixwebsocket/IXHttpClient.h>
|
||||
#include <jsoncpp/json/json.h>
|
||||
#include <regex>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class SentryClient
|
||||
{
|
||||
public:
|
||||
SentryClient(const std::string& dsn);
|
||||
~SentryClient() = default;
|
||||
|
||||
std::pair<HttpResponsePtr, std::string> send(const Json::Value& msg, bool verbose);
|
||||
|
||||
private:
|
||||
int64_t getTimestamp();
|
||||
std::string computeAuthHeader();
|
||||
std::string getIso8601();
|
||||
std::string computePayload(const Json::Value& msg);
|
||||
|
||||
Json::Value parseLuaStackTrace(const std::string& stack);
|
||||
|
||||
std::string _dsn;
|
||||
bool _validDsn;
|
||||
std::string _url;
|
||||
|
||||
// Used for authentication with a header
|
||||
std::string _publicKey;
|
||||
std::string _secretKey;
|
||||
|
||||
Json::FastWriter _jsonWriter;
|
||||
|
||||
std::regex _luaFrameRegex;
|
||||
|
||||
HttpClient _httpClient;
|
||||
};
|
||||
|
||||
} // namespace ix
|
Reference in New Issue
Block a user