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 return aKey1.file < aKey2.file || aKey1.line < aKey2.line || aKey1.func < aKey2.func || aKey1.cond < aKey2.cond;
324}
325
326
327void AddBreadcrumb( BREADCRUMB_TYPE aType, const wxString& aMsg, const wxString& aCategory, BREADCRUMB_LEVEL aLevel )
328{
329#ifdef KICAD_USE_SENTRY
330 if( !SENTRY::Instance()->IsOptedIn() )
331 {
332 return;
333 }
334
335 std::string type = GetSentryBreadCrumbType( aType );
336 sentry_value_t crumb = sentry_value_new_breadcrumb( type.c_str(), aMsg.c_str() );
337
338 sentry_value_set_by_key( crumb, "category", sentry_value_new_string( aCategory.c_str() ) );
339
340 if( aType == BREADCRUMB_TYPE::ERR )
341 {
342 std::string level = GetSentryBreadCrumbLevel( aLevel );
343 sentry_value_set_by_key( crumb, "level", sentry_value_new_string( level.c_str() ) );
344 }
345
346 sentry_add_breadcrumb( crumb );
347#endif
348}
349
350
351void AddNavigationBreadcrumb( const wxString& aMsg, const wxString& aCategory )
352{
353#ifdef KICAD_USE_SENTRY
355#endif
356}
357
358
359void AddTransactionBreadcrumb( const wxString& aMsg, const wxString& aCategory )
360{
361#ifdef KICAD_USE_SENTRY
363#endif
364}
365
366#ifdef KICAD_USE_SENTRY
367class TRANSACTION_IMPL
368{
369public:
370 TRANSACTION_IMPL( const std::string& aName, const std::string& aOperation )
371 {
372 m_ctx = sentry_transaction_context_new( aName.c_str(), aOperation.c_str() );
373 }
374
375 ~TRANSACTION_IMPL() {
376 Finish();
377
378 // note m_ctx is handled by sentry
379 }
380
381 void Start()
382 {
383 m_tx = sentry_transaction_start( m_ctx, sentry_value_new_null() );
384 }
385
386 void Finish()
387 {
388 FinishSpan();
389
390 if( m_tx )
391 {
392 sentry_transaction_finish( m_tx );
393 m_tx = nullptr;
394 }
395 }
396
397 void StartSpan( const std::string& aOperation, const std::string& aDescription )
398 {
399 if( m_span )
400 return;
401
402 if( !m_tx )
403 return;
404
405 m_span = sentry_transaction_start_child( m_tx, aOperation.c_str(), aDescription.c_str() );
406 }
407
408 void FinishSpan()
409 {
410 if( m_span )
411 {
412 sentry_span_finish( m_span );
413 m_span = nullptr;
414 }
415 }
416
417private:
418 sentry_transaction_context_t* m_ctx = nullptr;
419 sentry_transaction_t* m_tx = nullptr;
420 sentry_span_t* m_span = nullptr;
421};
422#endif
423}
424
425
426TRANSACTION::TRANSACTION( const std::string& aName, const std::string& aOperation )
427{
428#ifdef KICAD_USE_SENTRY
429 if( SENTRY::Instance()->IsOptedIn() )
430 {
431 m_impl = new TRANSACTION_IMPL( aName, aOperation );
432 }
433#endif
434}
435
436
438{
439#ifdef KICAD_USE_SENTRY
440 delete m_impl;
441#endif
442}
443
444
446{
447#ifdef KICAD_USE_SENTRY
448 if( m_impl )
449 {
450 m_impl->Start();
451 }
452#endif
453}
454
455
456void TRANSACTION::StartSpan( const std::string& aOperation, const std::string& aDescription )
457{
458#ifdef KICAD_USE_SENTRY
459 if( m_impl )
460 {
461 m_impl->StartSpan( aOperation, aDescription );
462 }
463#endif
464}
465
466
468{
469#ifdef KICAD_USE_SENTRY
470 if( m_impl )
471 {
472 m_impl->Finish();
473 }
474#endif
475}
476
477
479{
480#ifdef KICAD_USE_SENTRY
481 if( m_impl )
482 {
483 m_impl->FinishSpan();
484 }
485#endif
486}
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