| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "sentry-native.h" |
| 4 | #include "daemon/common.h" |
| 5 | |
| 6 | #include "sentry.h" |
| 7 | |
| 8 | static void nd_sentry_add_deadly_signal_as_breadcrumb(void); |
| 9 | |
| 10 | static char sentry_path[FILENAME_MAX] = ""; |
| 11 | |
| 12 | static bool sentry_initialized = false; |
| 13 | static bool breadcrumb_added = false; |
| 14 | static bool nd_sentry_crash_report_enabled = true; |
| 15 | |
| 16 | const char *nd_sentry_path(void) { |
| 17 | return sentry_path; |
| 18 | } |
| 19 | |
| 20 | void nd_sentry_crash_report(bool enable) { |
| 21 | nd_sentry_crash_report_enabled = enable; |
| 22 | } |
| 23 | |
| 24 | // -------------------------------------------------------------------------------------------------------------------- |
| 25 | // helpers |
| 26 | |
| 27 | static void nd_sentry_set_tag(const char *key, const char *value) { |
| 28 | if (!value || !*value) |
| 29 | return; |
| 30 | |
| 31 | sentry_set_tag(key, value); |
| 32 | } |
| 33 | |
| 34 | static void nd_sentry_set_tag_int64(const char *key, int64_t value) { |
| 35 | if(!value) |
| 36 | return; |
| 37 | |
| 38 | char buf[UINT64_MAX_LENGTH]; |
| 39 | print_int64(buf, value); |
| 40 | nd_sentry_set_tag(key, buf); |
| 41 | } |
| 42 | |
| 43 | static void nd_sentry_set_tag_uint64(const char *key, uint64_t value) { |
| 44 | if(!value) |
| 45 | return; |
| 46 | |
| 47 | char buf[UINT64_MAX_LENGTH]; |
| 48 | print_uint64(buf, value); |
| 49 | nd_sentry_set_tag(key, buf); |
| 50 | } |
| 51 | |
| 52 | static inline void nd_sentry_set_tag_uuid(const char *key, const ND_UUID uuid) { |
| 53 | if(UUIDiszero(uuid)) |
| 54 | return; |
| 55 | |
| 56 | char uuid_str[UUID_STR_LEN]; |
| 57 | uuid_unparse_lower(uuid.uuid, uuid_str); |
| 58 | nd_sentry_set_tag(key, uuid_str); |
| 59 | } |
| 60 | |
| 61 | static void nd_sentry_set_tag_uuid_compact(const char *key, const ND_UUID uuid) { |
| 62 | if(UUIDiszero(uuid)) |
| 63 | return; |
| 64 | |
| 65 | char uuid_str[UUID_COMPACT_STR_LEN]; |
| 66 | uuid_unparse_lower_compact(uuid.uuid, uuid_str); |
| 67 | nd_sentry_set_tag(key, uuid_str); |
| 68 | } |
| 69 | |
| 70 | static void nd_sentry_set_tag_uptime(void) { |
| 71 | nd_sentry_set_tag_int64("uptime", now_realtime_sec() - netdata_start_time); |
| 72 | } |
| 73 | |
| 74 | static void nd_sentry_set_tag_status(void) { |
| 75 | nd_sentry_set_tag("status", DAEMON_STATUS_2str(daemon_status_file_get_status())); |
| 76 | } |
| 77 | |
| 78 | // -------------------------------------------------------------------------------------------------------------------- |
| 79 | // sentry hooks |
| 80 | |
| 81 | static sentry_value_t nd_sentry_on_hook(sentry_value_t event) { |
| 82 | // IMPORTANT: this function is called from a signal handler |
| 83 | |
| 84 | // sentry enables a custom allocator for their use, |
| 85 | // that is async signal safe, so the sentry API is available here. |
| 86 | |
| 87 | if (!nd_sentry_crash_report_enabled) { |
| 88 | sentry_value_decref(event); |
| 89 | return sentry_value_new_null(); |
| 90 | } |
| 91 | |
| 92 | nd_sentry_add_deadly_signal_as_breadcrumb(); |
| 93 | |
| 94 | return event; |
| 95 | } |
| 96 | |
| 97 | static sentry_value_t nd_sentry_on_crash( |
| 98 | const sentry_ucontext_t *uctx __maybe_unused, // provides the user-space context of the crash |
| 99 | sentry_value_t event, // used the same way as in `before_send` |
| 100 | void *closure __maybe_unused) { // user-data that you can provide at configuration time |
| 101 | return nd_sentry_on_hook(event); |
| 102 | } |
| 103 | |
| 104 | static sentry_value_t nd_sentry_before_send( |
| 105 | sentry_value_t event, |
| 106 | void *hint __maybe_unused, |
| 107 | void *closure __maybe_unused) { |
| 108 | return nd_sentry_on_hook(event); |
| 109 | } |
| 110 | |
| 111 | // -------------------------------------------------------------------------------------------------------------------- |
| 112 | // sentry initialization |
| 113 | |
| 114 | void nd_sentry_init(void) { |
| 115 | if (!analytics_check_enabled() || sentry_initialized) |
| 116 | return; |
| 117 | |
| 118 | // path where sentry should save stuff |
| 119 | snprintfz(sentry_path, FILENAME_MAX - 1, "%s/.sentry-native", netdata_configured_cache_dir); |
| 120 | |
| 121 | // ---------------------------------------------------------------------------------------------------------------- |
| 122 | // sentry options |
| 123 | |
| 124 | sentry_options_t *options = sentry_options_new(); |
| 125 | sentry_options_set_dsn(options, NETDATA_SENTRY_DSN); |
| 126 | sentry_options_set_database_path(options, sentry_path); |
| 127 | sentry_options_set_environment(options, NETDATA_SENTRY_ENVIRONMENT); |
| 128 | |
| 129 | if(NETDATA_VERSION[0] == 'v') |
| 130 | sentry_options_set_release(options, &NETDATA_VERSION[1]); |
| 131 | else |
| 132 | sentry_options_set_release(options, NETDATA_VERSION); |
| 133 | |
| 134 | sentry_options_set_dist(options, NETDATA_SENTRY_DIST); |
| 135 | #ifdef NETDATA_INTERNAL_CHECKS |
| 136 | sentry_options_set_debug(options, 1); |
| 137 | #endif |
| 138 | |
| 139 | sentry_options_set_on_crash(options, nd_sentry_on_crash, NULL); |
| 140 | sentry_options_set_before_send(options, nd_sentry_before_send, NULL); |
| 141 | |
| 142 | // ---------------------------------------------------------------------------------------------------------------- |
| 143 | // initialization |
| 144 | |
| 145 | nd_cleanup_deadly_signals(); // remove our signal handlers, so that sentry will not hook back to us |
| 146 | sentry_init(options); |
| 147 | nd_initialize_signals(true); // add our signal handlers, we will hook back to sentry |
| 148 | |
| 149 | // ---------------------------------------------------------------------------------------------------------------- |
| 150 | // tags |
| 151 | |
| 152 | nd_sentry_set_tag("install_type", daemon_status_file_get_install_type()); |
| 153 | nd_sentry_set_tag("architecture", daemon_status_file_get_architecture()); |
| 154 | nd_sentry_set_tag("virtualization", daemon_status_file_get_virtualization()); |
| 155 | nd_sentry_set_tag("container", daemon_status_file_get_container()); |
| 156 | nd_sentry_set_tag("os_name", daemon_status_file_get_os_name()); |
| 157 | nd_sentry_set_tag("os_version", daemon_status_file_get_os_version()); |
| 158 | nd_sentry_set_tag("os_id", daemon_status_file_get_os_id()); |
| 159 | nd_sentry_set_tag("os_id_like", daemon_status_file_get_os_id_like()); |
| 160 | nd_sentry_set_tag("cloud_provider", daemon_status_file_get_cloud_provider_type()); |
| 161 | nd_sentry_set_tag("cloud_type", daemon_status_file_get_cloud_instance_type()); |
| 162 | nd_sentry_set_tag("cloud_region", daemon_status_file_get_cloud_instance_region()); |
| 163 | nd_sentry_set_tag("timezone", daemon_status_file_get_timezone()); |
| 164 | |
| 165 | nd_sentry_set_tag("hw_sys_vendor", daemon_status_file_get_sys_vendor()); |
| 166 | nd_sentry_set_tag("hw_product_name", daemon_status_file_get_product_name()); |
| 167 | nd_sentry_set_tag("hw_product_type", daemon_status_file_get_product_type()); |
| 168 | |
| 169 | // profile |
| 170 | CLEAN_BUFFER *profile = buffer_create(0, NULL); |
| 171 | ND_PROFILE_2buffer(profile, nd_profile_detect_and_configure(false), " "); |
| 172 | nd_sentry_set_tag("profile", buffer_tostring(profile)); |
| 173 | |
| 174 | // db_mode |
| 175 | nd_sentry_set_tag("db_mode", rrd_memory_mode_name(default_rrd_memory_mode)); |
| 176 | |
| 177 | // db_tiers |
| 178 | nd_sentry_set_tag_int64("db_tiers", (int64_t)nd_profile.storage_tiers); |
| 179 | |
| 180 | // ephemeral_id |
| 181 | nd_sentry_set_tag_uuid_compact("ephemeral_id", nd_log_get_invocation_id()); |
| 182 | |
| 183 | // agent_events_version |
| 184 | nd_sentry_set_tag("agent_events_version", TOSTRING(STATUS_FILE_VERSION)); |
| 185 | |
| 186 | nd_sentry_set_tag_uint64("restarts", daemon_status_file_get_restarts()); |
| 187 | nd_sentry_set_tag_int64("reliability", daemon_status_file_get_reliability()); |
| 188 | nd_sentry_set_tag("stack_traces", daemon_status_file_get_stack_trace_backend()); |
| 189 | |
| 190 | sentry_initialized = true; |
| 191 | } |
| 192 | |
| 193 | void nd_sentry_fini(void) { |
| 194 | if(!sentry_initialized) |
| 195 | return; |
| 196 | |
| 197 | sentry_close(); |
| 198 | } |
| 199 | |
| 200 | void nd_sentry_set_user(const char *guid) { |
| 201 | sentry_value_t user = sentry_value_new_object(); |
| 202 | sentry_value_set_by_key(user, "id", sentry_value_new_string(guid)); |
| 203 | sentry_set_user(user); |
| 204 | } |
| 205 | |
| 206 | // -------------------------------------------------------------------------------------------------------------------- |
| 207 | // sentry breadcrumbs |
| 208 | |
| 209 | static void nd_sentry_add_key_value_charp(sentry_value_t data, const char *key, const char *value) { |
| 210 | if (!value || !*value) |
| 211 | return; |
| 212 | |
| 213 | sentry_value_set_by_key(data, key, sentry_value_new_string(value)); |
| 214 | } |
| 215 | |
| 216 | static void nd_sentry_add_key_value_int64(sentry_value_t data, const char *key, int64_t value) { |
| 217 | if (!value) |
| 218 | return; |
| 219 | |
| 220 | char buf[UINT64_MAX_LENGTH]; |
| 221 | print_int64(buf, value); |
| 222 | |
| 223 | sentry_value_set_by_key(data, key, sentry_value_new_string(buf)); |
| 224 | } |
| 225 | |
| 226 | static void nd_sentry_add_key_value_uint64(sentry_value_t data, const char *key, uint64_t value) { |
| 227 | if (!value) |
| 228 | return; |
| 229 | |
| 230 | char buf[UINT64_MAX_LENGTH]; |
| 231 | print_uint64(buf, value); |
| 232 | |
| 233 | sentry_value_set_by_key(data, key, sentry_value_new_string(buf)); |
| 234 | } |
| 235 | |
| 236 | void nd_sentry_add_breadcrumb(const char *category, const char *message) { |
| 237 | if(!sentry_initialized || breadcrumb_added) |
| 238 | return; |
| 239 | |
| 240 | nd_sentry_set_tag_status(); |
| 241 | nd_sentry_set_tag_uptime(); |
| 242 | |
| 243 | nd_sentry_set_tag("thread", daemon_status_file_get_fatal_thread()); |
| 244 | nd_sentry_set_tag_uint64("thread_id", daemon_status_file_get_fatal_thread_id()); |
| 245 | nd_sentry_set_tag_uint64("worker_job_id", daemon_status_file_get_fatal_worker_job_id()); |
| 246 | |
| 247 | const char *function = daemon_status_file_get_fatal_function(); |
| 248 | if(!function || !*function) |
| 249 | function = category; |
| 250 | |
| 251 | // Set the transaction name to the function where the error occurred |
| 252 | // this should be low cardinality |
| 253 | sentry_set_transaction(function); |
| 254 | |
| 255 | // Set the fingerprint to the function where the error occurred |
| 256 | sentry_set_fingerprint("{{ default }}", function, NULL); |
| 257 | |
| 258 | sentry_value_t crumb = sentry_value_new_breadcrumb("fatal", message); |
| 259 | |
| 260 | sentry_value_t data = sentry_value_new_object(); |
| 261 | nd_sentry_add_key_value_charp(data, "message", daemon_status_file_get_fatal_message()); |
| 262 | nd_sentry_add_key_value_charp(data, "function", daemon_status_file_get_fatal_function()); |
| 263 | nd_sentry_add_key_value_charp(data, "filename", daemon_status_file_get_fatal_filename()); |
| 264 | nd_sentry_add_key_value_charp(data, "thread", daemon_status_file_get_fatal_thread()); |
| 265 | nd_sentry_add_key_value_uint64(data, "thread_id", daemon_status_file_get_fatal_thread_id()); |
| 266 | nd_sentry_add_key_value_int64(data, "line", daemon_status_file_get_fatal_line()); |
| 267 | nd_sentry_add_key_value_charp(data, "errno", daemon_status_file_get_fatal_errno()); |
| 268 | nd_sentry_add_key_value_charp(data, "stack_trace", daemon_status_file_get_fatal_stack_trace()); |
| 269 | nd_sentry_add_key_value_charp(data, "status", DAEMON_STATUS_2str(daemon_status_file_get_status())); |
| 270 | nd_sentry_add_key_value_uint64(data, "worker_job_id", daemon_status_file_get_fatal_worker_job_id()); |
| 271 | |
| 272 | sentry_value_set_by_key(crumb, "data", data); |
| 273 | sentry_add_breadcrumb(crumb); |
| 274 | } |
| 275 | |
| 276 | void nd_sentry_add_fatal_message_as_breadcrumb(void) { |
| 277 | nd_sentry_add_breadcrumb("fatal", "fatal message event details"); |
| 278 | } |
| 279 | |
| 280 | static void nd_sentry_add_deadly_signal_as_breadcrumb(void) { |
| 281 | nd_sentry_add_breadcrumb("deadly_signal", "deadly signal event details"); |
| 282 | } |
| 283 | |
| 284 | void nd_sentry_add_shutdown_timeout_as_breadcrumb(void) { |
| 285 | nd_sentry_add_breadcrumb("shutdown_timeout", "shutdown timeout event details"); |
| 286 | } |