Windows下sentry接入C/C++程序的詳細(xì)過程
sentry簡(jiǎn)介
首先,如果你是為工作而做,公司一定有自己的sentry賬號(hào),去找他們申請(qǐng)權(quán)限吧;如果是學(xué)習(xí)或自己用,先去這里申請(qǐng)一個(gè)賬號(hào)sentry.io。記得,綁定的郵箱很關(guān)鍵,之后的所有崩潰、消息、異常sentry都會(huì)發(fā)送郵件給你
sentry的作用
為大家通俗的總結(jié):針對(duì)開發(fā)中的項(xiàng)目,即使我們做足了一切的排查,可仍然有出現(xiàn)crash(崩潰)的可能,這是我們必須承認(rèn)的;或者,有些時(shí)候我們使用try catch,對(duì)于某些catch我們很希望知道它的觸發(fā)率是什么。因此,sentry的本質(zhì)就是做這樣的工作:為我們的工程上一層保險(xiǎn),讓我們開發(fā)人員能隨時(shí)知道自己維護(hù)的項(xiàng)目是怎樣的狀態(tài)
舉個(gè)例子:讓大家感受一下
例如:這里的項(xiàng)目,我們可以清楚的看到,崩潰觸發(fā)率等很多我們關(guān)心的內(nèi)容
下方的圖片,我們甚至可以清楚的定位到,本次crash發(fā)生在線程29428,SentryTest.exe中main函數(shù)的71行
這里只舉了兩個(gè)最最基本的例子,但已經(jīng)能看到sentry的強(qiáng)大。
sentry-native是什么
sentry作為一個(gè)開源的軟件,發(fā)展至今,已經(jīng)非常成熟。它支持的平臺(tái)眾多,甚至于針對(duì)不同的工作者(后臺(tái)、前端、客戶端)都有相應(yīng)的內(nèi)容。而我們本次討論的是針對(duì)C/C++的版本
sentry官方GitHub鏈接
基于C/C++的native版,SDK編譯
有一點(diǎn)我在這里吐槽一下:sentry的官方只給了源碼和編譯方法,為什么不送佛送到西,給咱們把SDK編譯好?。?!(如果你熟悉vckpg,其實(shí)vckpg可以直接集成sentry)
1.截至到我寫這篇博客,更新到0.5版本,使用可以將其對(duì)于的zip下載即可
2.開始編譯工作
我只能說,這里的編譯應(yīng)該是他們的疏忽,官方給的流程中有一些問題,我將整個(gè)流程進(jìn)行匯總,并將有問題的地方在后面標(biāo)注(希望在你使用的時(shí)候,這些問題已經(jīng)被修復(fù))
- sentry的SDK構(gòu)建借助的是CMake,在你下載的工程目錄下,打開cmd
如果你的開發(fā)環(huán)境是 macOS ,流程如下
# Configure the CMake build into the `build` directory with crashpad (the default # backend on macOS, thus optional to specify). Specifying `RelWithDebInfo` as the # `CMAKE_BUILD_TYPE` is also optional because it is the default in sentry-native # for all generators supporting it. cmake -B build -D SENTRY_BACKEND=crashpad -D CMAKE_BUILD_TYPE=RelWithDebInfo # build the project cmake --build build --parallel # install the resulting artifacts into a specific prefix cmake --install build --prefix install # which will result in the following (on macOS): exa --tree install --level 2 install ├── bin │ └── crashpad_handler ├── include │ └── sentry.h └── lib ├── cmake ├── libsentry.dylib └── libsentry.dylib.dSYM
如果你是在Windows下,流程如下
# The msbuild generator ignores the CMAKE_BUILD_TYPE because it contains all # build-types. Here we leave out the backend specification and rely on CMake # selecting crashpad as Windows' default backend. cmake -B build # The actual build step then requires we specify which build-type we want # to apply via the `--config` parameter. Please be aware that in msbuild # projects, the `--parallel` option has no effect. cmake --build build --config RelWithDebInfo # install the resulting artifacts (again requiring build-type!) cmake --install build --prefix install --config RelWithDebInfo # which will result in the following output (ignoring non-essential lines): tree /f install ├───bin │ crashpad_handler.exe │ crashpad_handler.pdb │ sentry.dll │ sentry.pdb │ ├───include │ sentry.h │ └───lib │ sentry.lib
接下來,進(jìn)行問題的解決(如果沒有問題,忽略這些內(nèi)容):
0.5版本整個(gè)工程編譯都會(huì)報(bào)警告:提示修改文件為Unicode。這是因?yàn)檫@些文件是他們?cè)贚inux下做的,所以文件都是UTF-8無簽名,而Windows下需要UTF-8帶簽名。但文件這么多,如果一個(gè)個(gè)在visual stdio下改不現(xiàn)實(shí),因此,打開CmakeList,將警告忽略,添加一句代碼:add_definitions(-w)
一定要注意,上圖的例子是我在Windows下做所以寫在這里,如果你是Mac下,就要寫在外面
工程里面給的example部分,竟然有語法錯(cuò)誤。。。這我真沒想到。解決辦法:自己打開visual stdio解決一下,或者另一個(gè)方式,忽略即可,只要正常編譯出來了dll、lib、exe即可。
或者打開build目錄,cmake成功后,會(huì)生成解決方案,用visual stdio打開,這里一定要將編譯方法選為下圖這個(gè)
接著,對(duì)每一個(gè)項(xiàng)目(有問題的RUN_TASK略過)進(jìn)行生成
如果你要編譯x86版本,除了在vs下將所以相關(guān)x64的設(shè)置都改成x86或win32,也許還會(huì)遇到這個(gè)錯(cuò)誤:模塊對(duì)于 SAFESEH 映像是不安全的錯(cuò)誤。解決方法是,對(duì)于出問題的模塊,屬性->鏈接器->命令行->添加:/SAFESEH:NO
暫時(shí)就這么多錯(cuò)誤,如果你還有其它的錯(cuò)誤,百度搜一搜,或問問大佬,別放棄,想想我當(dāng)時(shí)也很難的?。?!
sentry-SDK接入本地工程
接入我們自己的工程
接下來,我們將編譯好的SDK接入工程,注意:我所有的操作都是以Windows下例子,如果你是后端,去看對(duì)應(yīng)的官方文檔吧 sentry入口
ps:SDK,就是我們編譯出來的sentry.dll、sentry.lib、crashpad_handler.exe,sentry.h通俗來講就是可以直接集成到我們工程里的內(nèi)容。
接下來的操作,熟悉代碼編譯原理和程序運(yùn)行原理的朋友一定覺得我啰嗦
1.熟悉代碼編譯原理和程序運(yùn)行原理的朋友一定不陌生,一個(gè)接入的SDK想要成功編譯,需要將頭文件引入
2.lib庫是頭文件中那些函數(shù)的具體實(shí)現(xiàn),你需要在你的工程中將其鏈接進(jìn)來,可以使用 #pragma comment(lib, “sentry.lib”)手動(dòng)鏈接,亦可以在鏈接器中添加(一定要鏈接成功,否則編譯的時(shí)候會(huì)報(bào)錯(cuò))
3.sentry.dll是編譯出來的程序正常運(yùn)行需要的動(dòng)態(tài)庫,你需要將它和編譯出來的內(nèi)容放到同級(jí)目錄下,否則編譯出來的exe是無法正常運(yùn)行的。
4.crashpad_handler.exe,這是sentry執(zhí)行崩潰操作的exe,也就是說,我們調(diào)用的接口,最終都是它來執(zhí)行操作。因此,想讓sentry正常工作,需要將它和編譯產(chǎn)物放到一起(和sentry.dll一樣)。
關(guān)于配置
一些必要信息的初始化
記住一句話,sentry的初始化越早越好,始終記得,你的目的是讓sentry監(jiān)控你的項(xiàng)目狀態(tài)。
這里先上一段代碼吧
#include <sentry.h> int main(void) { sentry_options_t *options = sentry_options_new(); sentry_options_set_dsn(options, "https://examplePublicKey@o0.ingest.sentry.io/0"); sentry_options_set_release(options, "my-project-name@2.3.12"); sentry_options_set_debug(options, 1); sentry_options_set_environment(options, "production"); sentry_options_set_database_path(options, ".native"); sentry_init(options); /* ... */ }
上面的代碼完成了基本的初始化,也就是說sentry已經(jīng)可以正常工作了,接下來細(xì)說
1.初始化用到的對(duì)象
sentry_options_t *options = sentry_options_new();這段代碼中options就是后面需要用的對(duì)象,或許你可以把它的定義放到類內(nèi)成員函數(shù)處dsn
2.DSN告訴 SDK 將事件發(fā)送到哪里。如果未提供此值,SDK 將嘗試從SENTRY_DSN環(huán)境變量中讀取它。如果該變量也不存在,SDK 將不會(huì)發(fā)送任何事件。
關(guān)于dsn的查看如下圖,這個(gè)dsn是獨(dú)有的,也是固定的,因此代碼中可以直接寫死,即調(diào)用sentry_options_set_dsn接口,第一個(gè)參數(shù)就是初始化的對(duì)象,第二個(gè)參數(shù)即dsn地址,是一個(gè)字符串
3.Release版本
通常你的產(chǎn)品有多個(gè)不同版本,有些時(shí)候你可能還需要對(duì)它們進(jìn)行區(qū)分,如下圖,你可以清晰的看到每個(gè)版本的表現(xiàn)
使用sentry_options_set_release(參數(shù)1,參數(shù)2);參數(shù)1為初始化對(duì)象,參數(shù)2為具體版本(一個(gè)字符串)
4.觸發(fā)環(huán)境
有些時(shí)候,你的產(chǎn)品可能面向多種用戶,例如:product、develop、test。設(shè)置環(huán)境去進(jìn)行區(qū)分
sentry_options_set_environment(參數(shù)1,參數(shù)2);參數(shù)1為初始化對(duì)象,參數(shù)2為環(huán)境(一個(gè)字符串)
5.日志目錄
sentry的運(yùn)行是會(huì)產(chǎn)生目錄的,如果不設(shè)置,就會(huì)以當(dāng)前路徑為默認(rèn)目錄,在一些面向用戶的產(chǎn)品中,這是肯定不允許的,也許你需要將它設(shè)置為temp臨時(shí)目錄下去
sentry_options_set_database_path(參數(shù)1,參數(shù)2);參數(shù)1為初始化對(duì)象,參數(shù)2為目錄路徑(字符串,絕對(duì)路徑和相對(duì)路徑均可)
自定義配置
有些時(shí)候,你可能需要一些其它信息來輔助你排查錯(cuò)誤,或者是處理后續(xù)的事宜,這里列舉幾個(gè),可以選擇性參考
添加一個(gè)自定義content字段
例如上圖,這個(gè)character字段就是我自己添加的。
下面的函數(shù)是我自己封裝的函數(shù),第一個(gè)參數(shù)為自定義的內(nèi)容,例如:character,第二個(gè)參數(shù)為一個(gè)map,輸入你希望的信息,例如:uuid:19,name:Mighty Fighter,都是調(diào)用了sentry SDK接口。在需要添加信息的地方使用即可
void addContext(const char* contextName, std::unordered_map<char*, char*> textContent) { if (contextName == nullptr || textContent.empty()) { return; } sentry_value_t character = sentry_value_new_object(); for (auto it = textContent.begin(); it != textContent.end(); it++) { sentry_value_set_by_key(character, it->first, sentry_value_new_string(it->second)); } sentry_set_context(contextName, character); }
豐富本次觸發(fā)的用戶信息
下面的函數(shù)是我自定義的,為本次在觸發(fā)添加用戶信息,這里添加了觸發(fā)的ip地址和uuid,當(dāng)然你也可以自定義其它內(nèi)容。注意:sentry可以自動(dòng)拿到ip地址,格式如下面的代碼:sentry_value_new_string(“{{auto}}”)
void addUser(const char* ipAddress, const char* uuid) { if (uuid == nullptr) { return; } sentry_value_t user = sentry_value_new_object(); if (ipAddress == nullptr) { sentry_value_set_by_key(user, "ip_address", sentry_value_new_string("{{auto}}")); } else { sentry_value_set_by_key(user, "ip_address", sentry_value_new_string(ipAddress)); } sentry_value_set_by_key(user, "uuid", sentry_value_new_string(uuid)); LOG_INFO << "[SentryReporter::addUser]: add User: uuid: " << uuid; sentry_set_user(user); }
利用sentry監(jiān)視崩潰
在完成所有的初始化工作后,sentry就可以為你的工程進(jìn)行監(jiān)控了,在出現(xiàn)crash時(shí),即可向你的客戶端發(fā)送報(bào)告。
崩潰回調(diào)
sentry提供了崩潰回調(diào)函數(shù),什么作用呢?在發(fā)送crash時(shí),sentry會(huì)執(zhí)行它的所有工作,攔截+上發(fā),但在這之前,如果你想做一些其它的事情,也是可以的,看如下代碼:
sentry_value_t SentryReporter::onCrashCallback( const sentry_ucontext_t* uctx, // Crashed user space context sentry_value_t event, void* closure // User data that provide during configuration ) { const std::wstring uuid = APP->getMainWindow()->getUUID(); addUser(nullptr, Utils::StringUtils::ws2s(uuid).c_str()); //if you want to give up this crash,Add a judgment method and make the following if true if (GIVE_UP_CRASH) { sentry_value_decref(event); return sentry_value_new_null(); } return event; } //----------------初始化時(shí)添加------------------------- sentry_options_set_on_crash(optioning, onCrashCallback, NULL);
代碼中,實(shí)現(xiàn)了一個(gè)函數(shù)onCrashCallback,并在sentry初始化時(shí)進(jìn)行綁定,這樣做之后,就可以達(dá)到上面說的效果。
你可以在崩潰回調(diào)函數(shù)中做一些操作,例如:添加一些附加信息,又或者調(diào)用接口放棄上傳此次崩潰
一定要注意,sentry沒有做線程安全處理,因此這里的崩潰回調(diào)函數(shù)一定要輕量級(jí),不要做過重的計(jì)算操作
手動(dòng)發(fā)送異常、消息
sentry的另一種用法就是我們手動(dòng)上發(fā)事件,舉個(gè)例子:代碼中try catch,我希望得知某些catch的觸發(fā)率,就可以在對(duì)應(yīng)的catch處進(jìn)行一個(gè)手動(dòng)的數(shù)據(jù)上發(fā)。
看如下代碼:
void capturingErrorsWithStacktrace(const char* errorMessage) { sentry_value_t event = sentry_value_new_event(); sentry_value_t exc = sentry_value_new_exception("Exception", errorMessage); sentry_value_set_stacktrace(exc, NULL, 0); sentry_event_add_exception(event, exc); LOG_INFO << "[SentryReporter::capturingErrorsWithStacktrace]:sentry sent error,message: " << errorMessage; sentry_capture_event(event); } void capturingMessages(sentry_level_t level, const char* logger, const char* message) { if (logger == nullptr || message == nullptr) { return; } sentry_value_t event = sentry_value_new_message_event(level, logger, message); LOG_INFO << "[SentryReporter::capturingMessages]:sentry sent message,level: " << level << "logger: " << logger << "message: " << message; sentry_capture_event(event); }
上面的兩個(gè)函數(shù),一個(gè)可以手動(dòng)發(fā)送錯(cuò)誤、一個(gè)可以手動(dòng)發(fā)送一個(gè)自定義消息,這里就不講述了,自己看代碼理解一下。
消息回調(diào)
與crash回調(diào)一樣,手動(dòng)發(fā)送的消息也可以有回調(diào)函數(shù),這里直接給出實(shí)現(xiàn),原理請(qǐng)看上文:
/* * This function sends an error or message manually to make a callback * You can do some operations, such as adding information */ sentry_value_t SentryReporter::beforeSendCallback(sentry_value_t event, void* hint, void* closure) { //do something before send return event; } //---------------------設(shè)置發(fā)送消息回調(diào)-------------------------- sentry_options_set_before_send(m_pOptions.get(), beforeSendCallback, NULL);
上傳PDB,直接定位崩潰發(fā)生的位置
sentry的另一個(gè)強(qiáng)大之處,在于當(dāng)發(fā)生崩潰時(shí),它不僅可以及時(shí)捕捉并上報(bào),還可以配合正確的PDB(Windows下的調(diào)試文件),精確的定位到發(fā)生crash的exe或dll,甚至可以定位到具體哪一行代碼
- 適用于 iOS、iPadOS、tvOS、watchOS 和 macOS 的dSYM 文件
- 適用于 Linux 和 Android (NDK) 的ELF 符號(hào)
- 適用于 Windows 的PDB 文件
- 適用于所有平臺(tái)的Breakpad 符號(hào)
- WebAssembly 的WASM 文件
- 適用于 Java 和 Android 的ProGuard 映射
崩潰發(fā)生在線程29428,SentryTest.exe中main函數(shù)71行
流程如下:
1.最直接的方式是借助第三方的工具sentry-cli.exe,下載鏈接,下載下來的內(nèi)容僅僅一個(gè)sentry-cli.exe,在cmd下利用該exe進(jìn)行操作即可
2.首先,你可能需要先對(duì)身份進(jìn)行配置,cmd下該命令,其中如果你是使用的自己項(xiàng)目組或公司的sentry,需要將https://myserver.invalid/換為自己項(xiàng)目的url,這里放置的url是sentry io的官方環(huán)境
sentry-cli --url https://myserver.invalid/ login
3.上傳調(diào)試文件,這里以Windows下的pdb為例子
#首先對(duì)調(diào)試文件進(jìn)行檢查,是否格式正確 sentry-cli difutil check SentryTest.pdb
如果出現(xiàn)了類似以下內(nèi)容,即為格式正確
Debug Info File Check Type: elf debug companion Contained debug identifiers: > 924e148f-3bb7-06a0-74c1-36f42f08b40e (x86_64) Contained debug information: > symtab, debug Usable: yes
接下來,將該文件上傳,其中 < org >要換你的項(xiàng)目所在組織;< project > 要換位你的項(xiàng)目名稱;后面的files則為具體文件,支持絕對(duì)路徑和相對(duì)路徑,且支持上傳多個(gè)文件
sentry-cli upload-dif -o <org> -p <project> files...
出現(xiàn)類似如下內(nèi)容,則上傳成功
> Found 2 debug information files > Prepared debug information files for upload > Uploaded 2 missing debug information files > File processing complete: PENDING 1ddb3423-950a-3646-b17b-d4360e6acfc9 (MyApp; x86_64 executable) PENDING 1ddb3423-950a-3646-b17b-d4360e6acfc9 (MyApp; x86_64 debug companion)
又或者,你可以去sentry項(xiàng)目處查看:
博主自己封裝的sentry類
注意:這里的代碼是不能直接用的,因?yàn)樯婕暗搅艘恍┬畔⒌奶崛。枰鶕?jù)自身項(xiàng)目進(jìn)行適配,在對(duì)sentry有一定了解后,再來看
SentryReporter.h
#pragma once #include "sentry.h" #include "stdafx.h" #include <iostream> #include <string> #include <unordered_map> #ifdef ANDROID_SIX const std::string SENTRY_DSN = "https://b2a9969e03944fae81a12cf84afa6bd2@sentry.netease.com/81"; #else const std::string SENTRY_DSN = "https://ca88798ea2404243a3dc5a5e2ebdb670@sentry.netease.com/82"; #endif // ANDROID_SIX const std::string DATABASE_NAME = ".sentry-native"; const std::string ENVIRONMENT = "production"; const bool GIVE_UP_CRASH = false; struct SentryInitInfo { std::string dsn; std::string databasePath; std::string releaseVersion; std::string environment; std::string nemuDownloadLog; float sampleRate;//Range must be within :0-1 int maxBreadcrumbs;//default:100 bool attachStacktrace;//default:false }; class SentryReporter { public: SentryReporter(); ~SentryReporter(); void setup(); static void addContext(const char* contextName, std::unordered_map<char*, char*> textContent); static void addUser(const char* ipAddress, const char* uuid);//if ipAddress = nullptr,get from system static void capturingErrorsWithStacktrace(const char* errorMessage); static void capturingMessages(sentry_level_t level, const char* logger, const char* message); private: void fetchInitInfomation(); static sentry_value_t onCrashCallback(const sentry_ucontext_t* uctx, sentry_value_t event, void* closure); static sentry_value_t beforeSendCallback(sentry_value_t event, void* hint, void* closure); private: shared_ptr<sentry_options_t> m_pOptions; SentryInitInfo m_sentryInitInfo; };
SentryReporter.cpp
#include "SentryReport.h" #include "utils.h" #include "VersionNo.h" #include "MainWindow.h" sentry_value_t SentryReporter::onCrashCallback( const sentry_ucontext_t* uctx, // Crashed user space context sentry_value_t event, void* closure // User data that provide during configuration ) { const std::wstring uuid = APP->getMainWindow()->getUUID(); addUser(nullptr, Utils::StringUtils::ws2s(uuid).c_str()); //if you want to give up this crash,Add a judgment method and make the following if true if (GIVE_UP_CRASH) { sentry_value_decref(event); return sentry_value_new_null(); } return event; } /* * This function sends an error or message manually to make a callback * You can do some operations, such as adding information */ sentry_value_t SentryReporter::beforeSendCallback(sentry_value_t event, void* hint, void* closure) { //do something before send return event; } SentryReporter::SentryReporter() :m_pOptions(sentry_options_new()) { fetchInitInfomation(); } SentryReporter::~SentryReporter() { sentry_close(); m_pOptions = nullptr; } void SentryReporter::fetchInitInfomation() { m_sentryInitInfo.dsn = SENTRY_DSN; std::wstring databasePath = Utils::pathCombine(DCommon::DSystemUtils::getTempPath(), Utils::StringUtils::s2ws(DATABASE_NAME)); m_sentryInitInfo.databasePath = Utils::StringUtils::ws2s(databasePath); m_sentryInitInfo.releaseVersion = Utils::StringUtils::ws2s(STRVERSION); m_sentryInitInfo.environment = ENVIRONMENT; std::wstring logFilePath = Utils::getLogFile(); m_sentryInitInfo.nemuDownloadLog = Utils::StringUtils::ws2s(logFilePath); sentry_options_set_auto_session_tracking(m_pOptions.get(), false); sentry_options_set_symbolize_stacktraces(m_pOptions.get(), true); //use default value m_sentryInitInfo.sampleRate = 1; m_sentryInitInfo.maxBreadcrumbs = 100; m_sentryInitInfo.attachStacktrace = false; } void SentryReporter::setup() { sentry_options_set_dsn(m_pOptions.get(), m_sentryInitInfo.dsn.c_str()); sentry_options_set_database_path(m_pOptions.get(), m_sentryInitInfo.databasePath.c_str()); sentry_options_set_release(m_pOptions.get(), m_sentryInitInfo.releaseVersion.c_str()); sentry_options_set_environment(m_pOptions.get(), m_sentryInitInfo.environment.c_str()); sentry_options_set_traces_sample_rate(m_pOptions.get(), m_sentryInitInfo.sampleRate); sentry_options_add_attachment(m_pOptions.get(), m_sentryInitInfo.nemuDownloadLog.c_str()); sentry_options_set_on_crash(m_pOptions.get(), onCrashCallback, NULL); sentry_options_set_before_send(m_pOptions.get(), beforeSendCallback, NULL); int ret = sentry_init(m_pOptions.get()); if (ret != 0) { LOG_INFO << "[SentryReporter::setup]: sentry init error"; } } void SentryReporter::addContext(const char* contextName, std::unordered_map<char*, char*> textContent) { if (contextName == nullptr || textContent.empty()) { LOG_INFO << "[SentryReporter::addContext]: contextName==nullptr or textContent==nullptr"; return; } sentry_value_t character = sentry_value_new_object(); for (auto it = textContent.begin(); it != textContent.end(); it++) { sentry_value_set_by_key(character, it->first, sentry_value_new_string(it->second)); } sentry_set_context(contextName, character); } void SentryReporter::addUser(const char* ipAddress, const char* uuid) { if (uuid == nullptr) { LOG_INFO << "[SentryReporter::addUser]: uuid==nullptr"; return; } sentry_value_t user = sentry_value_new_object(); if (ipAddress == nullptr) { sentry_value_set_by_key(user, "ip_address", sentry_value_new_string("{{auto}}")); } else { sentry_value_set_by_key(user, "ip_address", sentry_value_new_string(ipAddress)); } sentry_value_set_by_key(user, "uuid", sentry_value_new_string(uuid)); LOG_INFO << "[SentryReporter::addUser]: add User: uuid: " << uuid; sentry_set_user(user); } void SentryReporter::capturingErrorsWithStacktrace(const char* errorMessage) { sentry_value_t event = sentry_value_new_event(); sentry_value_t exc = sentry_value_new_exception("Exception", errorMessage); sentry_value_set_stacktrace(exc, NULL, 0); sentry_event_add_exception(event, exc); LOG_INFO << "[SentryReporter::capturingErrorsWithStacktrace]:sentry sent error,message: " << errorMessage; sentry_capture_event(event); } void SentryReporter::capturingMessages(sentry_level_t level, const char* logger, const char* message) { if (logger == nullptr || message == nullptr) { return; } sentry_value_t event = sentry_value_new_message_event(level, logger, message); LOG_INFO << "[SentryReporter::capturingMessages]:sentry sent message,level: " << level << "logger: " << logger << "message: " << message; sentry_capture_event(event); }
到此這篇關(guān)于Windows——sentry接入C/C++程序的文章就介紹到這了,更多相關(guān)Windows C++程序內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c語言中main函數(shù)用法及知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給大家分享的是一篇關(guān)于c語言中main函數(shù)用法及知識(shí)點(diǎn)總結(jié)內(nèi)容,有需要的朋友們可以跟著學(xué)習(xí)參考下。2021-10-10