├── LICENSE ├── README.md ├── sentry.c └── uwsgiplugin.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 unbit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uwsgi-sentry 2 | uWSGI plugin for sentry integration 3 | 4 | Features 5 | ======== 6 | 7 | The plugin allows you to send events to a sentry server using the following uWSGI subsystems: 8 | 9 | * alarms 10 | * hooks 11 | * internal routing 12 | * exception handlers 13 | 14 | Installation 15 | ============ 16 | 17 | The plugin is uWSGI 2.0 friendly and requires libcurl development headers: 18 | 19 | ```sh 20 | uwsgi --build-plugin https://github.com/unbit/uwsgi-sentry 21 | ``` 22 | 23 | this will result in sentry_plugin.so in the current directory (copy it to your uwsgi plugins directory or ensure to specify its absolute path in the plugin option of your configuration) 24 | 25 | Sending alarms to sentry 26 | ======================== 27 | 28 | This is probably the core feature of the plugin. 29 | 30 | Once loaded the plugin exposes a "sentry" alarm engine (if you do not know what a uWSGI alarm is, check here http://uwsgi-docs.readthedocs.org/en/latest/AlarmSubsystem.html) 31 | 32 | Let's start defining an alarm for our socket listen queue 33 | 34 | ```ini 35 | [uwsgi] 36 | plugin = sentry 37 | 38 | alarm = lqfull sentry:dsn=https://b70a31b3510c4cf793964a185cfe1fd0:b7d80b520139450f903720eb7991bf3d@example.com/1,logger=uwsgi.sentry,culprit=listen_queue 39 | 40 | ; raise the 'lqfull' alarm when the listen queue is full 41 | alarm-listen-queue = lqfull 42 | 43 | ... 44 | ``` 45 | 46 | or you can track segfaults: 47 | 48 | ```ini 49 | [uwsgi] 50 | plugin = sentry 51 | 52 | alarm = segfault sentry:dsn=https://b70a31b3510c4cf793964a185cfe1fd0:b7d80b520139450f903720eb7991bf3d@example.com/1,logger=uwsgi.sentry,culprit=whoknows 53 | 54 | ; raise the 'segfault' alarm on sgementation fault 55 | alarm-segfault = segfault 56 | 57 | ... 58 | ``` 59 | 60 | or you can "track" logs: 61 | 62 | ```ini 63 | [uwsgi] 64 | plugin = sentry 65 | 66 | alarm = seen sentry:dsn=https://b70a31b3510c4cf793964a185cfe1fd0:b7d80b520139450f903720eb7991bf3d@example.com/1,logger=uwsgi.sentry,culprit=whoknows 67 | 68 | ; raise the 'seen' alarm whenever a log line starts with FOO 69 | alarm-log = seen ^FOO 70 | 71 | ... 72 | ``` 73 | 74 | And obviously you can combine it with the internal routing subsystem 75 | 76 | ```ini 77 | [uwsgi] 78 | plugin = sentry 79 | 80 | ; define a an alarm (named 'noputplease') when someone use the PUT method on the site 81 | alarm = noputplease sentry:dsn=https://b70a31b3510c4cf793964a185cfe1fd0:b7d80b520139450f903720eb7991bf3d@example.com/1,logger=uwsgi.sentry 82 | 83 | ; trigger the 'noputplease' alarm when PUT method is request 84 | route-if = isequal:${REQUEST_METHOD};PUT alarm:notputplease PUT IS NOT ALLOWED !!! 85 | ``` 86 | 87 | Triggering sentry events with hooks 88 | =================================== 89 | 90 | uWSGI hooks (http://uwsgi-docs.readthedocs.org/en/latest/Hooks.html) are special actions that can be triggered in the various server phases. 91 | 92 | The following example triggers a sentry event when the uWSGI instance starts and when its first worker is ready to accept requests: 93 | 94 | ```ini 95 | [uwsgi] 96 | plugin = sentry 97 | hook-asap = sentry:dsn=https://b70a31b3510c4cf793964a185cfe1fd0:b7d80b520139450f903720eb7991bf3d@example.com/1,logger=uwsgi.sentry,message=uWSGI IS STARTING 98 | hook-accepting1-once = sentry:dsn=https://b70a31b3510c4cf793964a185cfe1fd0:b7d80b520139450f903720eb7991bf3d@example.com/1,logger=uwsgi.sentry,message=uWSGI WORKER 1 IS READY 99 | ... 100 | ``` 101 | 102 | Managing Exceptions 103 | ==================== 104 | 105 | Note: this feature requires uWSGI >= 2.0.10 or to apply this patch: https://github.com/unbit/uwsgi/commit/c74428a3aa1aade1cca7e59b20c571a2a164dc13 (yeah a stupid bug) 106 | 107 | Note again: For managing exceptions use native sentry clients, use this feature when your language is not supported by sentry or the uWSGI request plugin is not a 'language handler' (like the GlusterFS, GridFS or Rados ones) but raises errors as exceptions. 108 | 109 | The uWSGI exception subsystem is an api exposed to plugins to have a common infrastructure for managing exceptions. 110 | 111 | It is not required for a plugin to support it, but common language-based ones (like python and ruby) supports it. 112 | 113 | To tell uWSGI to send exceptions to sentry use the `exception-handler` option: 114 | 115 | ```ini 116 | [uwsgi] 117 | plugin = sentry 118 | exception-handler = sentry:dsn=https://b70a31b3510c4cf793964a185cfe1fd0:b7d80b520139450f903720eb7991bf3d@example.com/1,logger=uwsgi.sentry 119 | ... 120 | ``` 121 | 122 | remember: exception handlers can be stacked, just specify multiple `exception-handler` options 123 | 124 | Advanced usage as internal routing action (dangerous) 125 | ==================================================== 126 | 127 | This is marked as "dangerous" because libcurl does a blocking session, so your request will be blocked while waiting for the sentry server to respond. 128 | 129 | By the way, in some cases it could be a good approach. The only exposed action is 'sentry': 130 | 131 | ```ini 132 | [uwsgi] 133 | plugin = sentry 134 | ; trigger a sentry event when a url starting with /help is requested 135 | route = ^/help sentry:dsn=https://b70a31b3510c4cf793964a185cfe1fd0:b7d80b520139450f903720eb7991bf3d@example.com/1,logger=uwsgi.sentry,message=help asked 136 | ... 137 | ``` 138 | 139 | Supported options 140 | ================= 141 | 142 | The following options can be specified on all sentry handlers (check sentry docs about their meaning, http://sentry.readthedocs.org/en/latest/developer/client/) : 143 | 144 | * dsn (required, specifies the dsn url of the sentry server) 145 | * level (set the level of the logging, can be fatal, error, warning, info, debug) 146 | * set the logger (example: uwsgi.sentry) 147 | * message (set/force the message, exceptions and alarms automatically set it to their values) 148 | * platform (the platform string) 149 | * culprit (the guilty one ;), generally it is the name of a function or a feature) 150 | * server_name (the name of the server, automatically set to hostname if empty) 151 | * release (the release string) 152 | * exception_type (the type of exception, automatically set by the exception handler) 153 | * exception_value (the value of the exception, automatically set by the exception handler) 154 | * no_verify (do not veryfy ssl server certificate) 155 | * debug (enable debug mode, report HTTP transactions in the logs) 156 | * tags (set tags, format is tags=key:value;key1:value1;key2:value2) 157 | * extra (set extra values, format is tags=key:value;key1:value1;key2:value2) 158 | * timeout (set http connection/response timeout, uses default socket timeout if not specified) 159 | -------------------------------------------------------------------------------- /sentry.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern struct uwsgi_server uwsgi; 5 | 6 | /* 7 | 8 | Sentry integration plugin, exposes: 9 | 10 | alarm handler 11 | internal routing action 12 | hook 13 | exception handler 14 | 15 | 16 | alarm = foobar sentry:dns=http:///,culprit=foobar,platform=test,... 17 | 18 | required fields: 19 | dsn 20 | event_id 21 | message 22 | timestamp 23 | level (fatal, error, warning, info, debug) 24 | logger 25 | 26 | additional: 27 | platform 28 | culprit 29 | server_name 30 | release 31 | tags 32 | extra 33 | exception 34 | 35 | Authentication: X-Sentry-Auth: Sentry sentry_version=5, sentry_timestamp=1329096377, 36 | sentry_key=b70a31b3510c4cf793964a185cfe1fd0, sentry_client=raven-python/1.0, 37 | sentry_secret=b7d80b520139450f903720eb7991bf3d 38 | 39 | 40 | 41 | */ 42 | 43 | struct sentry_config { 44 | char *dsn; 45 | 46 | char *url; 47 | char *key; 48 | char *secret; 49 | 50 | // this is a uuid (dashes will be removed) 51 | char event_id[37]; 52 | 53 | char *message; 54 | 55 | char *timestamp; 56 | 57 | char *level; 58 | 59 | char *logger; 60 | 61 | char *platform; 62 | 63 | char *culprit; 64 | 65 | char *server_name; 66 | 67 | char *release; 68 | 69 | char *no_verify; 70 | 71 | char *debug; 72 | 73 | char *exception_type; 74 | char *exception_value; 75 | 76 | char *tags; 77 | char *extra; 78 | char *timeout; 79 | }; 80 | 81 | #define skv(x) #x, &sc->x 82 | #define sc_free(x) if (sc->x) free(sc->x); 83 | 84 | static void sentry_request(struct sentry_config *, char *, size_t); 85 | 86 | static int sentry_config_do(char *arg, struct sentry_config *sc) { 87 | if (uwsgi_kvlist_parse(arg, strlen(arg), ',', '=', 88 | skv(dsn), 89 | skv(level), 90 | skv(logger), 91 | skv(message), 92 | skv(platform), 93 | skv(culprit), 94 | skv(server_name), 95 | skv(release), 96 | skv(no_verify), 97 | skv(debug), 98 | skv(exception_type), 99 | skv(exception_value), 100 | skv(tags), 101 | skv(extra), 102 | skv(timeout), 103 | NULL)) { 104 | uwsgi_log("[sentry] unable to parse sentry options\n"); 105 | return -1; 106 | } 107 | 108 | if (!sc->dsn) { 109 | uwsgi_log("[sentry] you need to specify a dsn\n"); 110 | return -1; 111 | } 112 | 113 | return 0 ; 114 | } 115 | 116 | static void sentry_config_free(struct sentry_config *sc) { 117 | sc_free(url); 118 | sc_free(key); 119 | sc_free(secret); 120 | 121 | sc_free(dsn); 122 | sc_free(level); 123 | sc_free(logger); 124 | sc_free(message); 125 | sc_free(platform); 126 | sc_free(culprit); 127 | sc_free(server_name); 128 | sc_free(release); 129 | 130 | sc_free(exception_type); 131 | sc_free(exception_value); 132 | 133 | sc_free(tags); 134 | sc_free(extra); 135 | sc_free(timeout); 136 | 137 | free(sc); 138 | } 139 | 140 | static int sentry_dsn_parse(struct sentry_config *sc) { 141 | // only http and https are supported 142 | size_t skip = 0; 143 | size_t dsn_len = strlen(sc->dsn); 144 | 145 | if (!uwsgi_starts_with(sc->dsn, dsn_len, "http://", 7)) { 146 | skip = 7; 147 | } 148 | 149 | if (!uwsgi_starts_with(sc->dsn, dsn_len, "https://", 8)) { 150 | skip = 8; 151 | } 152 | 153 | // find the @ 154 | char *at = strchr(sc->dsn+skip, '@'); 155 | if (!at) goto error; 156 | 157 | // find the last slash 158 | // does the dsn ends with a slash ? 159 | int ends_with_a_slash = 0; 160 | if (sc->dsn[dsn_len-1] == '/') { 161 | ends_with_a_slash = 1; 162 | sc->dsn[dsn_len-1] = 0; 163 | } 164 | 165 | char *last_slash = strrchr(at+1, '/'); 166 | if (ends_with_a_slash) sc->dsn[dsn_len-1] = '/'; 167 | if (!last_slash) goto error; 168 | 169 | size_t base_len = last_slash - (at+1); 170 | char *base = uwsgi_concat4n(sc->dsn, skip, at+1, base_len, "/api/", 5, last_slash+1, strlen(last_slash+1)); 171 | // append /store 172 | if (ends_with_a_slash) { 173 | sc->url = uwsgi_concat2(base, "store/"); 174 | } 175 | else { 176 | sc->url = uwsgi_concat2(base, "/store/"); 177 | } 178 | free(base); 179 | 180 | // now manage the auth part 181 | char *auth = sc->dsn+skip; 182 | size_t auth_len = at-auth; 183 | 184 | // find colon 185 | char *colon = memchr(auth, ':', auth_len); 186 | if (!colon) goto error; 187 | 188 | sc->key = uwsgi_concat2n(auth, colon-auth, "", 0); 189 | sc->secret = uwsgi_concat2n(colon+1, at-(colon+1), "", 0); 190 | 191 | if (sc->debug) { 192 | uwsgi_log("[sentry] parsed url: %s\n", sc->url); 193 | } 194 | 195 | return 0; 196 | error: 197 | uwsgi_log("[sentry] unable to parse dsn: %s\n", sc->dsn); 198 | return -1; 199 | } 200 | 201 | static void sentry_exception_parser(char *key, uint16_t keylen, char *value, uint16_t vallen, void *data) { 202 | struct sentry_config *sc = (struct sentry_config *) data; 203 | 204 | if (!uwsgi_strncmp(key, keylen, "class", 5)) { 205 | if (sc->exception_type) free(sc->exception_type); 206 | sc->exception_type = uwsgi_concat2n(value, vallen, "", 0); 207 | return; 208 | } 209 | 210 | if (!uwsgi_strncmp(key, keylen, "msg", 3)) { 211 | if (sc->exception_value) free(sc->exception_value); 212 | sc->exception_value = uwsgi_concat2n(value, vallen, "", 0); 213 | return; 214 | } 215 | 216 | if (!uwsgi_strncmp(key, keylen, "repr", 4)) { 217 | if (sc->message) free(sc->message); 218 | sc->message = uwsgi_concat2n(value, vallen, "", 0); 219 | return; 220 | } 221 | } 222 | 223 | static int sentry_exception_handler(struct uwsgi_exception_handler_instance *uehi, char *buf, size_t len) { 224 | struct sentry_config *sc = (struct sentry_config *) uehi->custom_ptr; 225 | if (!uehi->configured) { 226 | sc = uwsgi_calloc(sizeof(struct sentry_config)); 227 | if (sentry_config_do(uehi->arg, sc)) { 228 | goto error; 229 | } 230 | 231 | if (sentry_dsn_parse(sc)) { 232 | goto error; 233 | } 234 | 235 | uehi->custom_ptr = sc; 236 | uehi->configured = 1; 237 | } 238 | 239 | uwsgi_hooked_parse(buf, len, sentry_exception_parser, sc); 240 | 241 | // empty message in case sc->message is not defined 242 | sentry_request(sc, "", 0); 243 | 244 | return 0; 245 | error: 246 | sentry_config_free(sc); 247 | return -1; 248 | 249 | } 250 | 251 | static size_t sentry_curl_writefunc(void *ptr, size_t size, size_t nmemb, void *data) { 252 | struct sentry_config *sc = (struct sentry_config *) data; 253 | size_t len = size*nmemb; 254 | if (sc->debug) { 255 | uwsgi_log("%.*s\n", len, (char *)ptr); 256 | } 257 | return len; 258 | } 259 | 260 | static int sentry_add_kv(struct uwsgi_buffer *ub, char *items) { 261 | size_t i, argc = 0; 262 | char **argv = uwsgi_split_quoted(items, strlen(items), ";", &argc); 263 | if (!argv) return -1; 264 | 265 | int ret = -1; 266 | 267 | for(i=0;itimeout) { 300 | timeout = atoi(sc->timeout); 301 | } 302 | curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); 303 | curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout); 304 | curl_easy_setopt(curl, CURLOPT_URL, sc->url); 305 | 306 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, sentry_curl_writefunc); 307 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, sc); 308 | 309 | if (sc->no_verify) { 310 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); 311 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); 312 | } 313 | 314 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); 315 | 316 | // we need a uwsgi buffer to build the X-Sentry-Auth header 317 | ub_auth = uwsgi_buffer_new(uwsgi.page_size); 318 | if (uwsgi_buffer_append(ub_auth, "X-Sentry-Auth: Sentry sentry_version=5, sentry_timestamp=", 57)) goto end;; 319 | if (uwsgi_buffer_num64(ub_auth, now)) goto end; 320 | if (uwsgi_buffer_append(ub_auth, ", sentry_key=", 13)) goto end; 321 | if (uwsgi_buffer_append(ub_auth, sc->key, strlen(sc->key))) goto end; 322 | if (uwsgi_buffer_append(ub_auth, ", sentry_client=uwsgi-sentry, sentry_secret=", 44)) goto end; 323 | if (uwsgi_buffer_append(ub_auth, sc->secret, strlen(sc->secret))) goto end; 324 | // null ending 325 | if (uwsgi_buffer_append(ub_auth, "\0", 1)) goto end; 326 | 327 | header = curl_slist_append(headers, ub_auth->buf); 328 | if (!header) goto end; 329 | headers = header; 330 | 331 | header = curl_slist_append(headers, "Content-Type: application/json"); 332 | if (!header) goto end; 333 | headers = header; 334 | 335 | // disable Expect header 336 | header = curl_slist_append(headers, "Expect: "); 337 | if (!header) goto end; 338 | headers = header; 339 | 340 | // User-Agent 341 | header = curl_slist_append(headers, "User-Agent: uwsgi-sentry"); 342 | if (!header) goto end; 343 | headers = header; 344 | 345 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 346 | 347 | // prepare the json 348 | ub_body = uwsgi_buffer_new(uwsgi.page_size); 349 | // event_id, unfortunately we need to remove dashed from the uwsgi-generated uuid 350 | char uuid[37]; 351 | uwsgi_uuid(uuid); 352 | if (uwsgi_buffer_append(ub_body, "{\"event_id\":\"", 13)) goto end; 353 | if (uwsgi_buffer_append(ub_body, uuid, 8)) goto end; 354 | if (uwsgi_buffer_append(ub_body, uuid+9, 4)) goto end; 355 | if (uwsgi_buffer_append(ub_body, uuid+14, 4)) goto end; 356 | if (uwsgi_buffer_append(ub_body, uuid+19, 4)) goto end; 357 | if (uwsgi_buffer_append(ub_body, uuid+24, 12)) goto end; 358 | if (uwsgi_buffer_append(ub_body, "\"", 1)) goto end; 359 | 360 | if (sc->level) { 361 | if (uwsgi_buffer_append(ub_body, ",\"level\":\"", 10)) goto end; 362 | if (uwsgi_buffer_append_json(ub_body, sc->level, strlen(sc->level))) goto end; 363 | if (uwsgi_buffer_append(ub_body, "\"", 1)) goto end; 364 | } 365 | 366 | if (sc->logger) { 367 | if (uwsgi_buffer_append(ub_body, ",\"logger\":\"", 11)) goto end; 368 | if (uwsgi_buffer_append_json(ub_body, sc->logger, strlen(sc->logger))) goto end; 369 | if (uwsgi_buffer_append(ub_body, "\"", 1)) goto end; 370 | } 371 | 372 | if (sc->culprit) { 373 | if (uwsgi_buffer_append(ub_body, ",\"culprit\":\"", 12)) goto end; 374 | if (uwsgi_buffer_append_json(ub_body, sc->culprit, strlen(sc->culprit))) goto end; 375 | if (uwsgi_buffer_append(ub_body, "\"", 1)) goto end; 376 | } 377 | 378 | if (sc->platform) { 379 | if (uwsgi_buffer_append(ub_body, ",\"platform\":\"", 13)) goto end; 380 | if (uwsgi_buffer_append_json(ub_body, sc->platform, strlen(sc->platform))) goto end; 381 | if (uwsgi_buffer_append(ub_body, "\"", 1)) goto end; 382 | } 383 | 384 | if (sc->release) { 385 | if (uwsgi_buffer_append(ub_body, ",\"release\":\"", 12)) goto end; 386 | if (uwsgi_buffer_append_json(ub_body, sc->release, strlen(sc->release))) goto end; 387 | if (uwsgi_buffer_append(ub_body, "\"", 1)) goto end; 388 | } 389 | 390 | if (sc->tags) { 391 | if (uwsgi_buffer_append(ub_body, ",\"tags\":{", 9)) goto end; 392 | if (sentry_add_kv(ub_body, sc->tags)) goto end; 393 | if (uwsgi_buffer_append(ub_body, "}", 1)) goto end; 394 | } 395 | 396 | if (sc->extra) { 397 | if (uwsgi_buffer_append(ub_body, ",\"extra\":{", 10)) goto end; 398 | if (sentry_add_kv(ub_body, sc->extra)) goto end; 399 | if (uwsgi_buffer_append(ub_body, "}", 1)) goto end; 400 | } 401 | 402 | if (sc->exception_type || sc->exception_value) { 403 | if (uwsgi_buffer_append(ub_body, ",\"exception\":[{", 15)) goto end; 404 | if (sc->exception_type) { 405 | if (uwsgi_buffer_append(ub_body, "\"type\":\"", 8)) goto end; 406 | if (uwsgi_buffer_append_json(ub_body, sc->exception_type, strlen(sc->exception_type))) goto end; 407 | if (sc->exception_value) { 408 | if (uwsgi_buffer_append(ub_body, "\",", 2)) goto end; 409 | } 410 | else { 411 | if (uwsgi_buffer_append(ub_body, "\"", 1)) goto end; 412 | } 413 | } 414 | 415 | if (sc->exception_value) { 416 | if (uwsgi_buffer_append(ub_body, "\"value\":\"", 9)) goto end; 417 | if (uwsgi_buffer_append_json(ub_body, sc->exception_value, strlen(sc->exception_value))) goto end; 418 | if (uwsgi_buffer_append(ub_body, "\"", 1)) goto end; 419 | } 420 | 421 | if (uwsgi_buffer_append(ub_body, "}]", 2)) goto end; 422 | } 423 | 424 | if (uwsgi_buffer_append(ub_body, ",\"server_name\":\"", 16)) goto end; 425 | if (sc->server_name) { 426 | if (uwsgi_buffer_append_json(ub_body, sc->release, strlen(sc->release))) goto end; 427 | } 428 | else { 429 | if (uwsgi_buffer_append_json(ub_body, uwsgi.hostname, uwsgi.hostname_len)) goto end; 430 | } 431 | 432 | if (uwsgi_buffer_append(ub_body, "\",\"timestamp\":\"", 15)) goto end; 433 | char tm[sizeof("0000-00-00T00:00:00")]; 434 | strftime(tm, sizeof(tm), "%FT%T", gmtime(&now)); 435 | if (uwsgi_buffer_append_json(ub_body, tm, strlen(tm))) goto end; 436 | 437 | if (uwsgi_buffer_append(ub_body, "\",\"message\":\"", 13)) goto end; 438 | if (sc->message) { 439 | if (uwsgi_buffer_append_json(ub_body, sc->message, strlen(sc->message))) goto end; 440 | } 441 | else { 442 | if (uwsgi_buffer_append_json(ub_body, msg, len)) goto end; 443 | } 444 | if (uwsgi_buffer_append(ub_body, "\"}", 2)) goto end; 445 | 446 | 447 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ub_body->buf); 448 | curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, ub_body->pos); 449 | 450 | if (sc->debug) { 451 | uwsgi_log("[sentry] sending %.*s to %s\n", ub_body->pos, ub_body->buf, sc->url); 452 | } 453 | 454 | CURLcode res = curl_easy_perform(curl); 455 | 456 | if (res != CURLE_OK) { 457 | uwsgi_log_verbose("[sentry] error sending request: %.*s\n", ub_body->pos, ub_body->buf); 458 | } 459 | else { 460 | long http_code = 0; 461 | #ifdef CURLINFO_RESPONSE_CODE 462 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); 463 | #else 464 | curl_easy_getinfo(curl, CURLINFO_HTTP_CODE, &http_code); 465 | #endif 466 | if (http_code != 200) { 467 | uwsgi_log_verbose("[sentry] HTTP api returned non-200 response code: %d\n", (int) http_code); 468 | } 469 | } 470 | 471 | end: 472 | if (headers) curl_slist_free_all(headers); 473 | if (ub_auth) uwsgi_buffer_destroy(ub_auth); 474 | curl_easy_cleanup(curl); 475 | } 476 | 477 | static void sentry_alarm_func(struct uwsgi_alarm_instance *uai, char *msg, size_t len) { 478 | struct sentry_config *sc = (struct sentry_config *) uai->data_ptr; 479 | sentry_request(sc, msg, len); 480 | } 481 | 482 | static void sentry_alarm_init(struct uwsgi_alarm_instance *uai) { 483 | struct sentry_config *sc = uwsgi_calloc(sizeof(struct sentry_config)); 484 | if (sentry_config_do(uai->arg, sc)) { 485 | // useless :P 486 | sentry_config_free(sc); 487 | exit(1); 488 | } 489 | 490 | if (sentry_dsn_parse(sc)) { 491 | // useless again :P 492 | sentry_config_free(sc); 493 | exit(1); 494 | } 495 | uai->data_ptr = sc; 496 | } 497 | 498 | static int sentry_hook(char *arg) { 499 | int ret = -1; 500 | struct sentry_config *sc = uwsgi_calloc(sizeof(struct sentry_config)); 501 | if (sentry_config_do(arg, sc)) { 502 | goto end; 503 | } 504 | 505 | if (sentry_dsn_parse(sc)) { 506 | goto end; 507 | } 508 | 509 | // empty message in case sc->message is not defined 510 | // we do not check for errors here as we do not want to destroy the 511 | // instace if the sentry server is down 512 | sentry_request(sc, "", 0); 513 | ret = 0; 514 | end: 515 | sentry_config_free(sc); 516 | return ret; 517 | } 518 | 519 | #ifdef UWSGI_ROUTING 520 | static int sentry_router_func(struct wsgi_request *wsgi_req, struct uwsgi_route *ur) { 521 | char **subject = (char **) (((char *)(wsgi_req))+ur->subject); 522 | uint16_t *subject_len = (uint16_t *) (((char *)(wsgi_req))+ur->subject_len); 523 | 524 | struct uwsgi_buffer *ub = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, ur->data, ur->data_len); 525 | // continue even on memory error 526 | if (!ub) return UWSGI_ROUTE_CONTINUE; 527 | 528 | struct sentry_config *sc = uwsgi_calloc(sizeof(struct sentry_config)); 529 | if (sentry_config_do(ub->buf, sc)) { 530 | goto end; 531 | } 532 | 533 | if (sentry_dsn_parse(sc)) { 534 | goto end; 535 | } 536 | 537 | // empty message in case sc->message is not defined 538 | sentry_request(sc, "", 0); 539 | end: 540 | sentry_config_free(sc); 541 | uwsgi_buffer_destroy(ub); 542 | return UWSGI_ROUTE_CONTINUE; 543 | } 544 | 545 | static int sentry_router(struct uwsgi_route *ur, char *args) { 546 | ur->func = sentry_router_func; 547 | ur->data = args; 548 | ur->data_len = strlen(args); 549 | return 0; 550 | } 551 | #endif 552 | 553 | static void sentry_register() { 554 | uwsgi_register_exception_handler("sentry", sentry_exception_handler); 555 | uwsgi_register_alarm("sentry", sentry_alarm_init, sentry_alarm_func); 556 | uwsgi_register_hook("sentry", sentry_hook); 557 | #ifdef UWSGI_ROUTING 558 | uwsgi_register_router("sentry", sentry_router); 559 | #endif 560 | } 561 | 562 | struct uwsgi_plugin sentry_plugin = { 563 | .name = "sentry", 564 | .on_load = sentry_register, 565 | }; 566 | -------------------------------------------------------------------------------- /uwsgiplugin.py: -------------------------------------------------------------------------------- 1 | NAME='sentry' 2 | LIBS=['-lcurl'] 3 | GCC_LIST=['sentry'] 4 | --------------------------------------------------------------------------------