KiCad PCB EDA Suite
Loading...
Searching...
No Matches
app_monitor.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <app_monitor.h>
21#include <pgm_base.h>
22#include <magic_enum.hpp>
23#include <kiplatform/policy.h>
24#include <policy_keys.h>
25#include <paths.h>
26#include <wx/filename.h>
27#include <wx/ffile.h>
28
29#include <boost/uuid/uuid_io.hpp>
30#include <boost/uuid/uuid_generators.hpp>
31#include <build_version.h>
32
33#ifdef KICAD_USE_SENTRY
34#include <sentry.h>
35#endif
36
37using namespace APP_MONITOR;
38
39
41 m_isOptedIn( false )
42{
43}
44
45
47{
48#ifdef KICAD_USE_SENTRY
49 sentryInit();
50#endif
51}
52
53
55{
56#ifdef KICAD_USE_SENTRY
57 sentry_close();
58#endif
59}
60
61
62void SENTRY::AddTag( const wxString& aKey, const wxString& aValue )
63{
64#ifdef KICAD_USE_SENTRY
65 sentry_set_tag( aKey.c_str(), aValue.c_str() );
66#endif
67}
68
69
70void SENTRY::SetSentryOptIn( bool aOptIn )
71{
72#ifdef KICAD_USE_SENTRY
73 if( aOptIn )
74 {
75 // Opting in (when not opted in before) may require reading the uid off disk
76 // as it was skipped previously.
78
79 if( !m_sentry_optin_fn.Exists() )
80 {
81 wxFFile sentryInitFile( m_sentry_optin_fn.GetFullPath(), "w" );
82 sentryInitFile.Write( "" );
83 sentryInitFile.Close();
84 }
85
86 m_isOptedIn = true;
87 }
88 else
89 {
90 if( m_sentry_optin_fn.Exists() )
91 {
92 wxRemoveFile( m_sentry_optin_fn.GetFullPath() );
93 sentry_close();
94 }
95
96 m_isOptedIn = false;
97 }
98#else
99 m_isOptedIn = false;
100#endif
101}
102
103
105{
106 boost::uuids::uuid uuid = boost::uuids::random_generator()();
107 wxString userGuid = boost::uuids::to_string( uuid );
108
109 wxFFile sentryInitFile( m_sentry_uid_fn.GetFullPath(), "w" );
110 sentryInitFile.Write( userGuid );
111 sentryInitFile.Close();
112
113 return userGuid;
114}
115
116
121
122
123const wxString& SENTRY::GetSentryId()
124{
125 return m_sentryUid;
126}
127
128
130{
131 if( m_sentry_optin_fn.Exists() )
132 {
133 wxFFile sentryInitFile( m_sentry_uid_fn.GetFullPath() );
134 sentryInitFile.ReadAll( &m_sentryUid );
135 sentryInitFile.Close();
136 }
137
138 if( m_sentryUid.IsEmpty() || m_sentryUid.length() != 36 )
139 {
141 }
142}
143
144
146{
147#ifdef KICAD_USE_SENTRY
148 m_sentry_optin_fn = wxFileName( PATHS::GetUserCachePath(), "sentry-opt-in" );
149 m_sentry_uid_fn = wxFileName( PATHS::GetUserCachePath(), "sentry-uid" );
150
151 if( isConfiguredOptedIn() )
152 {
153 m_isOptedIn = true;
154
156
157 sentry_options_t* options = sentry_options_new();
158
159#ifndef KICAD_SENTRY_DSN
160#error "Project configuration error, missing KICAD_SENTRY_DSN"
161#endif
162 // only capture 5% of transactions
163 sentry_options_set_traces_sample_rate( options, 0.05 );
164 sentry_options_set_dsn( options, KICAD_SENTRY_DSN );
165
166#ifdef __WXMAC__
167 wxString handlerPath = PATHS::GetExecutablePath() + wxT( "Contents/MacOS/crashpad_handler" );
168
169 if( wxFileExists( handlerPath ) )
170 sentry_options_set_handler_path( options, handlerPath.c_str() );
171#endif
172
173 wxFileName tmp;
174 tmp.AssignDir( PATHS::GetUserCachePath() );
175 tmp.AppendDir( "sentry" );
176
177#ifdef __WINDOWS__
178 sentry_options_set_database_pathw( options, tmp.GetPathWithSep().wc_str() );
179#else
180 sentry_options_set_database_path( options, tmp.GetPathWithSep().c_str() );
181#endif
182 sentry_options_set_symbolize_stacktraces( options, true );
183 sentry_options_set_auto_session_tracking( options, false );
184
185 sentry_options_set_release( options, GetCommitHash().ToStdString().c_str() );
186
187 // This just gives us more filtering within sentry, issues still get grouped across
188 // environments.
189 sentry_options_set_environment( options, GetMajorMinorVersion().c_str() );
190
191 sentry_init( options );
192
193 sentry_value_t user = sentry_value_new_object();
194 sentry_value_set_by_key( user, "id", sentry_value_new_string( m_sentryUid.c_str() ) );
195 sentry_set_user( user );
196
197 sentry_set_tag( "kicad.version", GetBuildVersion().ToStdString().c_str() );
198 }
199#endif
200}
201
202
204{
205 // policy takes precedence
208 {
209 return policyState == KIPLATFORM::POLICY::PBOOL::ENABLED;
210 }
211
212 return m_sentry_optin_fn.Exists();
213}
214
215
217{
218#ifdef KICAD_USE_SENTRY
219 // always override with policy, just in case
222 {
223 return policyState == KIPLATFORM::POLICY::PBOOL::ENABLED;
224 }
225
226 return m_isOptedIn;
227#else
228 return false;
229#endif
230}
231
232
233void SENTRY::LogAssert( const ASSERT_CACHE_KEY& aKey, const wxString& aAssertMsg )
234{
235#ifdef KICAD_USE_SENTRY
237 {
238 return;
239 }
240
241 // We use an assert cache to avoid collecting too many events
242 // Because they can come from functions that are called hundreds of times in loops
243 if( m_assertCache.find( aKey ) == m_assertCache.end() )
244 {
245 sentry_value_t exc = sentry_value_new_exception( "assert", aAssertMsg.c_str() );
246 sentry_value_set_stacktrace( exc, NULL, 0 );
247
248 sentry_value_t sentryEvent = sentry_value_new_event();
249 sentry_event_add_exception( sentryEvent, exc );
250 sentry_capture_event( sentryEvent );
251 m_assertCache.insert( aKey );
252 }
253#endif
254}
255
256
257void SENTRY::LogException( const wxString& aMsg, bool aUnhandled )
258{
259#ifdef KICAD_USE_SENTRY
261 {
262 return;
263 }
264
265 sentry_scope_t* local_scope = sentry_local_scope_new();
266 sentry_scope_set_tag( local_scope, "unhandled", aUnhandled ? "true" : "false" );
267
268 sentry_value_t exc = sentry_value_new_exception( "exception", aMsg.c_str() );
269 sentry_value_set_stacktrace( exc, NULL, 0 );
270
271 sentry_value_t sentryEvent = sentry_value_new_event();
272 sentry_event_add_exception( sentryEvent, exc );
273 sentry_capture_event_with_scope( sentryEvent, local_scope );
274#endif
275}
276
277#ifdef KICAD_USE_SENTRY
278static std::string GetSentryBreadCrumbType( BREADCRUMB_TYPE aType )
279{
280 // the only special case due to collisions with defines
281 if( aType == BREADCRUMB_TYPE::DBG )
282 return "debug";
283
284 if( aType == BREADCRUMB_TYPE::ERR )
285 return "error";
286
287 std::string ret( magic_enum::enum_name( aType ) );
288
289 std::transform( ret.begin(), ret.end(), ret.begin(),
290 []( unsigned char c )
291 {
292 return std::tolower( c );
293 } );
294
295 return ret;
296}
297
298
299static std::string GetSentryBreadCrumbLevel( BREADCRUMB_LEVEL aLevel )
300{
301 // the only special case due to collisions with defines
302 if( aLevel == BREADCRUMB_LEVEL::DBG )
303 return "debug";
304
305 if( aLevel == BREADCRUMB_LEVEL::ERR )
306 return "error";
307
308 std::string ret( magic_enum::enum_name( aLevel ) );
309
310 std::transform( ret.begin(), ret.end(), ret.begin(),
311 []( unsigned char c )
312 {
313 return std::tolower( c );
314 } );
315
316 return ret;
317}
318#endif
319
320namespace APP_MONITOR
321{
322SENTRY* SENTRY::m_instance = nullptr;
323
324
325bool operator<( const ASSERT_CACHE_KEY& aKey1, const ASSERT_CACHE_KEY& aKey2 )
326{
327 if( aKey1.file != aKey2.file )
328 return aKey1.file < aKey2.file;
329
330 if( aKey1.line != aKey2.line )
331 return aKey1.line < aKey2.line;
332
333 if( aKey1.func != aKey2.func )
334 return aKey1.func < aKey2.func;
335
336 return aKey1.cond < aKey2.cond;
337}
338
339
340void AddBreadcrumb( BREADCRUMB_TYPE aType, const wxString& aMsg, const wxString& aCategory, BREADCRUMB_LEVEL aLevel )
341{
342#ifdef KICAD_USE_SENTRY
343 if( !SENTRY::Instance()->IsOptedIn() )
344 {
345 return;
346 }
347
348 std::string type = GetSentryBreadCrumbType( aType );
349 sentry_value_t crumb = sentry_value_new_breadcrumb( type.c_str(), aMsg.c_str() );
350
351 sentry_value_set_by_key( crumb, "category", sentry_value_new_string( aCategory.c_str() ) );
352
353 if( aType == BREADCRUMB_TYPE::ERR )
354 {
355 std::string level = GetSentryBreadCrumbLevel( aLevel );
356 sentry_value_set_by_key( crumb, "level", sentry_value_new_string( level.c_str() ) );
357 }
358
359 sentry_add_breadcrumb( crumb );
360#endif
361}
362
363
364void AddNavigationBreadcrumb( const wxString& aMsg, const wxString& aCategory )
365{
366#ifdef KICAD_USE_SENTRY
368#endif
369}
370
371
372void AddTransactionBreadcrumb( const wxString& aMsg, const wxString& aCategory )
373{
374#ifdef KICAD_USE_SENTRY
376#endif
377}
378
379#ifdef KICAD_USE_SENTRY
380class TRANSACTION_IMPL
381{
382public:
383 TRANSACTION_IMPL( const std::string& aName, const std::string& aOperation )
384 {
385 m_ctx = sentry_transaction_context_new( aName.c_str(), aOperation.c_str() );
386 }
387
388 ~TRANSACTION_IMPL() {
389 Finish();
390
391 // note m_ctx is handled by sentry
392 }
393
394 void Start()
395 {
396 m_tx = sentry_transaction_start( m_ctx, sentry_value_new_null() );
397 }
398
399 void Finish()
400 {
401 FinishSpan();
402
403 if( m_tx )
404 {
405 sentry_transaction_finish( m_tx );
406 m_tx = nullptr;
407 }
408 }
409
410 void StartSpan( const std::string& aOperation, const std::string& aDescription )
411 {
412 if( m_span )
413 return;
414
415 if( !m_tx )
416 return;
417
418 m_span = sentry_transaction_start_child( m_tx, aOperation.c_str(), aDescription.c_str() );
419 }
420
421 void FinishSpan()
422 {
423 if( m_span )
424 {
425 sentry_span_finish( m_span );
426 m_span = nullptr;
427 }
428 }
429
430private:
431 sentry_transaction_context_t* m_ctx = nullptr;
432 sentry_transaction_t* m_tx = nullptr;
433 sentry_span_t* m_span = nullptr;
434};
435#endif
436}
437
438
439TRANSACTION::TRANSACTION( const std::string& aName, const std::string& aOperation )
440{
441#ifdef KICAD_USE_SENTRY
442 if( SENTRY::Instance()->IsOptedIn() )
443 {
444 m_impl = new TRANSACTION_IMPL( aName, aOperation );
445 }
446#endif
447}
448
449
451{
452#ifdef KICAD_USE_SENTRY
453 delete m_impl;
454#endif
455}
456
457
459{
460#ifdef KICAD_USE_SENTRY
461 if( m_impl )
462 {
463 m_impl->Start();
464 }
465#endif
466}
467
468
469void TRANSACTION::StartSpan( const std::string& aOperation, const std::string& aDescription )
470{
471#ifdef KICAD_USE_SENTRY
472 if( m_impl )
473 {
474 m_impl->StartSpan( aOperation, aDescription );
475 }
476#endif
477}
478
479
481{
482#ifdef KICAD_USE_SENTRY
483 if( m_impl )
484 {
485 m_impl->Finish();
486 }
487#endif
488}
489
490
492{
493#ifdef KICAD_USE_SENTRY
494 if( m_impl )
495 {
496 m_impl->FinishSpan();
497 }
498#endif
499}
wxString GetMajorMinorVersion()
Get only the major and minor version in a string major.minor.
wxString GetCommitHash()
Get the commit hash as a string.
wxString GetBuildVersion()
Get the full KiCad version string.
This is a singleton class intended to manage sentry.
std::set< ASSERT_CACHE_KEY > m_assertCache
wxString sentryCreateUid()
wxFileName m_sentry_uid_fn
void LogAssert(const ASSERT_CACHE_KEY &aKey, const wxString &aMsg)
void SetSentryOptIn(bool aOptIn)
void LogException(const wxString &aMsg, bool aUnhandled)
void AddTag(const wxString &aKey, const wxString &aValue)
static SENTRY * m_instance
wxFileName m_sentry_optin_fn
static SENTRY * Instance()
const wxString & GetSentryId()
void StartSpan(const std::string &aOperation, const std::string &aDescription)
TRANSACTION(const std::string &aName, const std::string &aOperation)
static wxString GetUserCachePath()
Gets the stock (install) 3d viewer plugins path.
Definition paths.cpp:460
static const wxString & GetExecutablePath()
Definition paths.cpp:671
void AddTransactionBreadcrumb(const wxString &aMsg, const wxString &aCategory)
Add a transaction breadcrumb.
void AddBreadcrumb(BREADCRUMB_TYPE aType, const wxString &aMsg, const wxString &aCategory, BREADCRUMB_LEVEL aLevel)
Add a sentry breadcrumb.
void AddNavigationBreadcrumb(const wxString &aMsg, const wxString &aCategory)
Add a navigation breadcrumb.
bool operator<(const ASSERT_CACHE_KEY &aKey1, const ASSERT_CACHE_KEY &aKey2)
PBOOL GetPolicyBool(const wxString &aKey)
see class PGM_BASE
#define POLICY_KEY_DATACOLLECTION
Definition policy_keys.h:26
This struct represents a key being used for the std::set that deduplicates asserts during this runnin...
Definition app_monitor.h:86