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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include <app_monitor.h>
25#include <pgm_base.h>
26#include <magic_enum.hpp>
27#include <kiplatform/policy.h>
28#include <policy_keys.h>
29#include <paths.h>
30#include <wx/filename.h>
31#include <wx/ffile.h>
32
33#include <boost/uuid/uuid_io.hpp>
34#include <boost/uuid/uuid_generators.hpp>
35#include <build_version.h>
36
37#ifdef KICAD_USE_SENTRY
38#include <sentry.h>
39#endif
40
41using namespace APP_MONITOR;
42
44 m_isOptedIn( false )
45{
46}
47
48
50{
51#ifdef KICAD_USE_SENTRY
52 sentryInit();
53#endif
54}
55
56
58{
59#ifdef KICAD_USE_SENTRY
60 sentry_close();
61#endif
62}
63
64
65void SENTRY::AddTag( const wxString& aKey, const wxString& aValue )
66{
67#ifdef KICAD_USE_SENTRY
68 sentry_set_tag( aKey.c_str(), aValue.c_str() );
69#endif
70}
71
72
73void SENTRY::SetSentryOptIn( bool aOptIn )
74{
75#ifdef KICAD_USE_SENTRY
76 if( aOptIn )
77 {
78 // Opting in (when not opted in before) may require reading the uid off disk
79 // as it was skipped previously.
81
82 if( !m_sentry_optin_fn.Exists() )
83 {
84 wxFFile sentryInitFile( m_sentry_optin_fn.GetFullPath(), "w" );
85 sentryInitFile.Write( "" );
86 sentryInitFile.Close();
87 }
88
89 m_isOptedIn = true;
90 }
91 else
92 {
93 if( m_sentry_optin_fn.Exists() )
94 {
95 wxRemoveFile( m_sentry_optin_fn.GetFullPath() );
96 sentry_close();
97 }
98
99 m_isOptedIn = false;
100 }
101#else
102 m_isOptedIn = false;
103#endif
104}
105
106
108{
109 boost::uuids::uuid uuid = boost::uuids::random_generator()();
110 wxString userGuid = boost::uuids::to_string( uuid );
111
112 wxFFile sentryInitFile( m_sentry_uid_fn.GetFullPath(), "w" );
113 sentryInitFile.Write( userGuid );
114 sentryInitFile.Close();
115
116 return userGuid;
117}
118
119
124
125
126const wxString& SENTRY::GetSentryId()
127{
128 return m_sentryUid;
129}
130
131
133{
134 if( m_sentry_optin_fn.Exists() )
135 {
136 wxFFile sentryInitFile( m_sentry_uid_fn.GetFullPath() );
137 sentryInitFile.ReadAll( &m_sentryUid );
138 sentryInitFile.Close();
139 }
140
141 if( m_sentryUid.IsEmpty() || m_sentryUid.length() != 36 )
142 {
144 }
145}
146
147
149{
150#ifdef KICAD_USE_SENTRY
151 m_sentry_optin_fn = wxFileName( PATHS::GetUserCachePath(), "sentry-opt-in" );
152 m_sentry_uid_fn = wxFileName( PATHS::GetUserCachePath(), "sentry-uid" );
153
154 if( isConfiguredOptedIn() )
155 {
156 m_isOptedIn = true;
157
159
160 sentry_options_t* options = sentry_options_new();
161
162#ifndef KICAD_SENTRY_DSN
163#error "Project configuration error, missing KICAD_SENTRY_DSN"
164#endif
165 // only capture 5% of transactions
166 sentry_options_set_traces_sample_rate( options, 0.05 );
167 sentry_options_set_dsn( options, KICAD_SENTRY_DSN );
168
169 wxFileName tmp;
170 tmp.AssignDir( PATHS::GetUserCachePath() );
171 tmp.AppendDir( "sentry" );
172
173#ifdef __WINDOWS__
174 sentry_options_set_database_pathw( options, tmp.GetPathWithSep().wc_str() );
175#else
176 sentry_options_set_database_path( options, tmp.GetPathWithSep().c_str() );
177#endif
178 sentry_options_set_symbolize_stacktraces( options, true );
179 sentry_options_set_auto_session_tracking( options, false );
180
181 sentry_options_set_release( options, GetCommitHash().ToStdString().c_str() );
182
183 // This just gives us more filtering within sentry, issues still get grouped across
184 // environments.
185 sentry_options_set_environment( options, GetMajorMinorVersion().c_str() );
186
187 sentry_init( options );
188
189 sentry_value_t user = sentry_value_new_object();
190 sentry_value_set_by_key( user, "id", sentry_value_new_string( m_sentryUid.c_str() ) );
191 sentry_set_user( user );
192
193 sentry_set_tag( "kicad.version", GetBuildVersion().ToStdString().c_str() );
194 }
195#endif
196}
197
198
200{
201 // policy takes precedence
204 {
205 return policyState == KIPLATFORM::POLICY::PBOOL::ENABLED;
206 }
207
208 return m_sentry_optin_fn.Exists();
209}
210
211
213{
214#ifdef KICAD_USE_SENTRY
215 // always override with policy, just in case
218 {
219 return policyState == KIPLATFORM::POLICY::PBOOL::ENABLED;
220 }
221
222 return m_isOptedIn;
223#else
224 return false;
225#endif
226}
227
228
229void SENTRY::LogAssert( const ASSERT_CACHE_KEY& aKey, const wxString& aAssertMsg )
230{
231#ifdef KICAD_USE_SENTRY
233 {
234 return;
235 }
236
237 // We use an assert cache to avoid collecting too many events
238 // Because they can come from functions that are called hundreds of times in loops
239 if( m_assertCache.find( aKey ) == m_assertCache.end() )
240 {
241 sentry_value_t exc = sentry_value_new_exception( "assert", aAssertMsg.c_str() );
242 sentry_value_set_stacktrace( exc, NULL, 0 );
243
244 sentry_value_t sentryEvent = sentry_value_new_event();
245 sentry_event_add_exception( sentryEvent, exc );
246 sentry_capture_event( sentryEvent );
247 m_assertCache.insert( aKey );
248 }
249#endif
250}
251
252
253void SENTRY::LogException( const wxString& aMsg, bool aUnhandled )
254{
255#ifdef KICAD_USE_SENTRY
257 {
258 return;
259 }
260
261 sentry_scope_t* local_scope = sentry_local_scope_new();
262 sentry_scope_set_tag( local_scope, "unhandled", aUnhandled ? "true" : "false" );
263
264 sentry_value_t exc = sentry_value_new_exception( "exception", aMsg.c_str() );
265 sentry_value_set_stacktrace( exc, NULL, 0 );
266
267 sentry_value_t sentryEvent = sentry_value_new_event();
268 sentry_event_add_exception( sentryEvent, exc );
269 sentry_capture_event_with_scope( sentryEvent, local_scope );
270#endif
271}
272
273#ifdef KICAD_USE_SENTRY
274static std::string GetSentryBreadCrumbType( BREADCRUMB_TYPE aType )
275{
276 // the only special case due to collisions with defines
277 if( aType == BREADCRUMB_TYPE::DBG )
278 return "debug";
279
280 if( aType == BREADCRUMB_TYPE::ERR )
281 return "error";
282
283 std::string ret( magic_enum::enum_name( aType ) );
284
285 std::transform( ret.begin(), ret.end(), ret.begin(),
286 []( unsigned char c )
287 {
288 return std::tolower( c );
289 } );
290
291 return ret;
292}
293
294
295static std::string GetSentryBreadCrumbLevel( BREADCRUMB_LEVEL aLevel )
296{
297 // the only special case due to collisions with defines
298 if( aLevel == BREADCRUMB_LEVEL::DBG )
299 return "debug";
300
301 if( aLevel == BREADCRUMB_LEVEL::ERR )
302 return "error";
303
304 std::string ret( magic_enum::enum_name( aLevel ) );
305
306 std::transform( ret.begin(), ret.end(), ret.begin(),
307 []( unsigned char c )
308 {
309 return std::tolower( c );
310 } );
311
312 return ret;
313}
314#endif
315
316namespace APP_MONITOR
317{
318SENTRY* SENTRY::m_instance = nullptr;
319
320
321bool operator<( const ASSERT_CACHE_KEY& aKey1, const ASSERT_CACHE_KEY& aKey2 )
322{
323 if( aKey1.file != aKey2.file )
324 return aKey1.file < aKey2.file;
325
326 if( aKey1.line != aKey2.line )
327 return aKey1.line < aKey2.line;
328
329 if( aKey1.func != aKey2.func )
330 return aKey1.func < aKey2.func;
331
332 return aKey1.cond < aKey2.cond;
333}
334
335
336void AddBreadcrumb( BREADCRUMB_TYPE aType, const wxString& aMsg, const wxString& aCategory, BREADCRUMB_LEVEL aLevel )
337{
338#ifdef KICAD_USE_SENTRY
339 if( !SENTRY::Instance()->IsOptedIn() )
340 {
341 return;
342 }
343
344 std::string type = GetSentryBreadCrumbType( aType );
345 sentry_value_t crumb = sentry_value_new_breadcrumb( type.c_str(), aMsg.c_str() );
346
347 sentry_value_set_by_key( crumb, "category", sentry_value_new_string( aCategory.c_str() ) );
348
349 if( aType == BREADCRUMB_TYPE::ERR )
350 {
351 std::string level = GetSentryBreadCrumbLevel( aLevel );
352 sentry_value_set_by_key( crumb, "level", sentry_value_new_string( level.c_str() ) );
353 }
354
355 sentry_add_breadcrumb( crumb );
356#endif
357}
358
359
360void AddNavigationBreadcrumb( const wxString& aMsg, const wxString& aCategory )
361{
362#ifdef KICAD_USE_SENTRY
364#endif
365}
366
367
368void AddTransactionBreadcrumb( const wxString& aMsg, const wxString& aCategory )
369{
370#ifdef KICAD_USE_SENTRY
372#endif
373}
374
375#ifdef KICAD_USE_SENTRY
376class TRANSACTION_IMPL
377{
378public:
379 TRANSACTION_IMPL( const std::string& aName, const std::string& aOperation )
380 {
381 m_ctx = sentry_transaction_context_new( aName.c_str(), aOperation.c_str() );
382 }
383
384 ~TRANSACTION_IMPL() {
385 Finish();
386
387 // note m_ctx is handled by sentry
388 }
389
390 void Start()
391 {
392 m_tx = sentry_transaction_start( m_ctx, sentry_value_new_null() );
393 }
394
395 void Finish()
396 {
397 FinishSpan();
398
399 if( m_tx )
400 {
401 sentry_transaction_finish( m_tx );
402 m_tx = nullptr;
403 }
404 }
405
406 void StartSpan( const std::string& aOperation, const std::string& aDescription )
407 {
408 if( m_span )
409 return;
410
411 if( !m_tx )
412 return;
413
414 m_span = sentry_transaction_start_child( m_tx, aOperation.c_str(), aDescription.c_str() );
415 }
416
417 void FinishSpan()
418 {
419 if( m_span )
420 {
421 sentry_span_finish( m_span );
422 m_span = nullptr;
423 }
424 }
425
426private:
427 sentry_transaction_context_t* m_ctx = nullptr;
428 sentry_transaction_t* m_tx = nullptr;
429 sentry_span_t* m_span = nullptr;
430};
431#endif
432}
433
434
435TRANSACTION::TRANSACTION( const std::string& aName, const std::string& aOperation )
436{
437#ifdef KICAD_USE_SENTRY
438 if( SENTRY::Instance()->IsOptedIn() )
439 {
440 m_impl = new TRANSACTION_IMPL( aName, aOperation );
441 }
442#endif
443}
444
445
447{
448#ifdef KICAD_USE_SENTRY
449 delete m_impl;
450#endif
451}
452
453
455{
456#ifdef KICAD_USE_SENTRY
457 if( m_impl )
458 {
459 m_impl->Start();
460 }
461#endif
462}
463
464
465void TRANSACTION::StartSpan( const std::string& aOperation, const std::string& aDescription )
466{
467#ifdef KICAD_USE_SENTRY
468 if( m_impl )
469 {
470 m_impl->StartSpan( aOperation, aDescription );
471 }
472#endif
473}
474
475
477{
478#ifdef KICAD_USE_SENTRY
479 if( m_impl )
480 {
481 m_impl->Finish();
482 }
483#endif
484}
485
486
488{
489#ifdef KICAD_USE_SENTRY
490 if( m_impl )
491 {
492 m_impl->FinishSpan();
493 }
494#endif
495}
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:450
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:30
This struct represents a key being used for the std::set that deduplicates asserts during this runnin...
Definition app_monitor.h:90