agoo 0.9.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of agoo might be problematic. Click here for more details.

data/ext/agoo/http.h ADDED
@@ -0,0 +1,13 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #ifndef __AGOO_HTTP_H__
4
+ #define __AGOO_HTTP_H__
5
+
6
+ #include <stdbool.h>
7
+
8
+ extern void http_init();
9
+ extern void http_header_ok(const char *key, int klen, const char *value, int vlen);
10
+
11
+ extern const char* http_code_message(int code);
12
+
13
+ #endif // __AGOO_HTTP_H__
data/ext/agoo/log.c ADDED
@@ -0,0 +1,497 @@
1
+ // Copyright 2018 by Peter Ohler, All Rights Reserved
2
+
3
+ #include <dirent.h>
4
+ #include <stdio.h>
5
+ #include <stdlib.h>
6
+ #include <string.h>
7
+ #include <sys/stat.h>
8
+ #include <sys/types.h>
9
+ #include <time.h>
10
+
11
+ #include "dtime.h"
12
+ #include "log.h"
13
+
14
+ // lower gives faster response but burns more CPU. This is a reasonable compromise.
15
+ #define RETRY_SECS 0.0001
16
+ #define NOT_WAITING 0
17
+ #define WAITING 1
18
+ #define NOTIFIED 2
19
+ #define RESET_COLOR "\033[0m"
20
+ #define RESET_SIZE 4
21
+
22
+ static const char log_name[] = "agoo.log";
23
+ static const char log_prefix[] = "agoo.log.";
24
+ static const char log_format[] = "%s/agoo.log.%d";
25
+
26
+ static struct _Color colors[] = {
27
+ { .name = "black", .ansi = "\033[30;1m" },
28
+ { .name = "red", .ansi = "\033[31;1m" },
29
+ { .name = "green", .ansi = "\033[32;1m" },
30
+ { .name = "yellow", .ansi = "\033[33;1m" },
31
+ { .name = "blue", .ansi = "\033[34;1m" },
32
+ { .name = "magenta", .ansi = "\033[35;1m" },
33
+ { .name = "cyan", .ansi = "\033[36;1m" },
34
+ { .name = "white", .ansi = "\033[37;1m" },
35
+ { .name = "gray", .ansi = "\033[37m" },
36
+ { .name = "dark_red", .ansi = "\033[31m" },
37
+ { .name = "dark_green", .ansi = "\033[32m" },
38
+ { .name = "brown", .ansi = "\033[33m" },
39
+ { .name = "dark_blue", .ansi = "\033[34m" },
40
+ { .name = "purple", .ansi = "\033[35m" },
41
+ { .name = "dark_cyan", .ansi = "\033[36m" },
42
+ { .name = NULL, .ansi = NULL }
43
+ };
44
+
45
+ static const char level_chars[] = { 'F', 'E', 'W', 'I', 'D', '?' };
46
+
47
+ static Color
48
+ find_color(const char *name) {
49
+ if (NULL != name) {
50
+ for (Color c = colors; NULL != c->name; c++) {
51
+ if (0 == strcasecmp(c->name, name)) {
52
+ return c;
53
+ }
54
+ }
55
+ }
56
+ return NULL;
57
+ }
58
+
59
+ static bool
60
+ log_queue_empty(Log log) {
61
+ LogEntry head = atomic_load(&log->head);
62
+ LogEntry next = head + 1;
63
+
64
+ if (log->end <= next) {
65
+ next = log->q;
66
+ }
67
+ if (!head->ready && log->tail == next) {
68
+ return true;
69
+ }
70
+ return false;
71
+ }
72
+
73
+ static LogEntry
74
+ log_queue_pop(Log log, double timeout) {
75
+ LogEntry e = log->head;
76
+ LogEntry next;
77
+
78
+ if (e->ready) {
79
+ return e;
80
+ }
81
+ next = log->head + 1;
82
+ if (log->end <= next) {
83
+ next = log->q;
84
+ }
85
+ // If the next is the tail then wait for something to be appended.
86
+ for (int cnt = (int)(timeout / RETRY_SECS); atomic_load(&log->tail) == next; cnt--) {
87
+ // TBD poll would be better
88
+ if (cnt <= 0) {
89
+ return NULL;
90
+ }
91
+ dsleep(RETRY_SECS);
92
+ }
93
+ atomic_store(&log->head, next);
94
+
95
+ return log->head;
96
+ }
97
+
98
+
99
+ static int
100
+ jwrite(LogEntry e, FILE *file) {
101
+ // TBD make e->what JSON friendly
102
+ return fprintf(file, "{\"when\":%lld.%09lld,\"where\":\"%s\",\"level\":%d,\"what\":\"%s\"}\n",
103
+ (long long)(e->when / 1000000000LL),
104
+ (long long)(e->when % 1000000000LL),
105
+ e->cat->label,
106
+ e->cat->level,
107
+ (NULL == e->whatp ? e->what : e->whatp));
108
+ }
109
+
110
+ //I 2015/05/23 11:22:33.123456789 label: The contents of the what field.
111
+ static int
112
+ classic_write(Log log, LogEntry e, FILE *file) {
113
+ time_t t = (time_t)(e->when / 1000000000LL);
114
+ int hour = 0;
115
+ int min = 0;
116
+ int sec = 0;
117
+ long long frac = (long long)e->when % 1000000000LL;
118
+ char levelc = level_chars[e->cat->level];
119
+ int cnt = 0;
120
+
121
+ t += log->zone;
122
+ if (log->day_start <= t && t < log->day_end) {
123
+ t -= log->day_start;
124
+ hour = t / 3600;
125
+ min = t % 3600 / 60;
126
+ sec = t % 60;
127
+ } else {
128
+ struct tm *tm = gmtime(&t);
129
+
130
+ hour = tm->tm_hour;
131
+ min = tm->tm_min;
132
+ sec = tm->tm_sec;
133
+ sprintf(log->day_buf, "%04d/%02d/%02d ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
134
+ log->day_start = t - (hour * 3600 + min * 60 + sec);
135
+ log->day_end = log->day_start + 86400;
136
+ }
137
+ if (log->colorize) {
138
+ cnt = fprintf(file, "%s%c %s%02d:%02d:%02d.%09lld %s: %s%s\n",
139
+ e->cat->color->ansi, levelc, log->day_buf, hour, min, sec, frac,
140
+ e->cat->label,
141
+ (NULL == e->whatp ? e->what : e->whatp),
142
+ RESET_COLOR);
143
+ } else {
144
+ cnt += fprintf(file, "%c %s%02d:%02d:%02d.%09lld %s: %s\n",
145
+ levelc, log->day_buf, hour, min, sec, frac,
146
+ e->cat->label,
147
+ (NULL == e->whatp ? e->what : e->whatp));
148
+ }
149
+ return cnt;
150
+ }
151
+
152
+ // Remove all file with sequence numbers higher than max_files. max_files is
153
+ // max number of archived version. It does not include the primary.
154
+ static void
155
+ remove_old_logs(Log log) {
156
+ struct dirent *de;
157
+ long seq;
158
+ char *end;
159
+ char path[1024];
160
+ DIR *dir = opendir(log->dir);
161
+
162
+ while (NULL != (de = readdir(dir))) {
163
+ if ('.' == *de->d_name || '\0' == *de->d_name) {
164
+ continue;
165
+ }
166
+ if (0 != strncmp(log_prefix, de->d_name, sizeof(log_prefix) - 1)) {
167
+ continue;
168
+ }
169
+ // Don't remove the primary log file.
170
+ if (0 == strcmp(log_name, de->d_name)) {
171
+ continue;
172
+ }
173
+ seq = strtol(de->d_name + sizeof(log_prefix) - 1, &end, 10);
174
+ if (log->max_files < seq) {
175
+ snprintf(path, sizeof(path), "%s/%s", log->dir, de->d_name);
176
+ remove(path);
177
+ }
178
+ }
179
+ closedir(dir);
180
+ }
181
+
182
+ static int
183
+ rotate(Err err, Log log) {
184
+ char from[1024];
185
+ char to[1024];
186
+
187
+ if (NULL != log->file) {
188
+ fclose(log->file);
189
+ log->file = NULL;
190
+ }
191
+ for (int seq = log->max_files; 0 < seq; seq--) {
192
+ snprintf(to, sizeof(to), log_format, log->dir, seq + 1);
193
+ snprintf(from, sizeof(from), log_format, log->dir, seq);
194
+ rename(from, to);
195
+ }
196
+ snprintf(to, sizeof(to), log_format, log->dir, 1);
197
+ snprintf(from, sizeof(from), "%s/%s", log->dir, log_name);
198
+ rename(from, to);
199
+
200
+ log->file = fopen(from, "w");
201
+ log->size = 0;
202
+
203
+ remove_old_logs(log);
204
+
205
+ return ERR_OK;
206
+ }
207
+
208
+ static void*
209
+ loop(void *ctx) {
210
+ Log log = (Log)ctx;
211
+ LogEntry e;
212
+
213
+ while (!log->done || !log_queue_empty(log)) {
214
+ if (NULL != (e = log_queue_pop(log, 0.5))) {
215
+ if (log->console) {
216
+ if (log->classic) {
217
+ classic_write(log, e, stdout);
218
+ } else {
219
+ jwrite(e, stdout);
220
+ }
221
+ }
222
+ if (NULL != log->file) {
223
+ if (log->classic) {
224
+ log->size += classic_write(log, e, log->file);
225
+ } else {
226
+ log->size += jwrite(e, log->file);
227
+ }
228
+ if (log->max_size <= log->size) {
229
+ rotate(NULL, log);
230
+ }
231
+ }
232
+ if (NULL != e->whatp) {
233
+ free(e->whatp);
234
+ }
235
+ e->ready = false;
236
+ }
237
+ }
238
+ return NULL;
239
+ }
240
+
241
+ bool
242
+ log_flush(Log log, double timeout) {
243
+ timeout += dtime();
244
+
245
+ while (!log->done && !log_queue_empty(log)) {
246
+ if (timeout < dtime()) {
247
+ return false;
248
+ }
249
+ dsleep(0.001);
250
+ }
251
+ if (NULL != log->file) {
252
+ fflush(log->file);
253
+ }
254
+ return true;
255
+ }
256
+
257
+ static int
258
+ configure(Err err, Log log, VALUE options) {
259
+ if (Qnil != options) {
260
+ VALUE v;
261
+
262
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_dir"))))) {
263
+ rb_check_type(v, T_STRING);
264
+ strncpy(log->dir, StringValuePtr(v), sizeof(log->dir));
265
+ log->dir[sizeof(log->dir) - 1] = '\0';
266
+ }
267
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_max_files"))))) {
268
+ int max = FIX2INT(v);
269
+
270
+ if (1 <= max || max < 100) {
271
+ log->max_files = max;
272
+ } else {
273
+ rb_raise(rb_eArgError, "log_max_files must be between 1 and 100.");
274
+ }
275
+ }
276
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_max_size"))))) {
277
+ int max = FIX2INT(v);
278
+
279
+ if (1 <= max) {
280
+ log->max_size = max;
281
+ } else {
282
+ rb_raise(rb_eArgError, "log_max_size must be 1 or more.");
283
+ }
284
+ }
285
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_console"))))) {
286
+ log->console = (Qtrue == v);
287
+ }
288
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_classic"))))) {
289
+ log->classic = (Qtrue == v);
290
+ }
291
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_colorize"))))) {
292
+ log->colorize = (Qtrue == v);
293
+ }
294
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_states"))))) {
295
+ if (T_HASH == rb_type(v)) {
296
+ LogCat cat = log->cats;
297
+ VALUE cv;
298
+
299
+ for (; NULL != cat; cat = cat->next) {
300
+ if (Qnil != (cv = rb_hash_lookup(v, ID2SYM(rb_intern(cat->label))))) {
301
+ if (Qtrue == cv) {
302
+ cat->on = true;
303
+ } else if (Qfalse == cv) {
304
+ cat->on = false;
305
+ }
306
+ }
307
+ }
308
+ } else {
309
+ rb_raise(rb_eArgError, "log_states must be a Hash.");
310
+ }
311
+ }
312
+ }
313
+ return ERR_OK;
314
+ }
315
+
316
+ static int
317
+ open_log_file(Err err, Log log) {
318
+ char path[1024];
319
+
320
+ snprintf(path, sizeof(path), "%s/%s", log->dir, log_name);
321
+
322
+ log->file = fopen(path, "a");
323
+ if (NULL == log->file) {
324
+ return err_no(err, "failed to open '%s'.", path);
325
+ }
326
+ log->size = ftell(log->file);
327
+ if (log->max_size <= log->size) {
328
+ return rotate(err, log);
329
+ }
330
+ return ERR_OK;
331
+ }
332
+
333
+ int
334
+ log_init(Err err, Log log, VALUE cfg) {
335
+ time_t t = time(NULL);
336
+ struct tm *tm = localtime(&t);
337
+ int qsize = 1024;
338
+
339
+ //log->cats = NULL; done outside of here
340
+ *log->dir = '\0';
341
+ log->file = NULL;
342
+ log->max_files = 3;
343
+ log->max_size = 100000000; // 100M
344
+ log->size = 0;
345
+ log->done = false;
346
+ log->console = true;
347
+ log->classic = true;
348
+ log->colorize = true;
349
+ log->zone = (int64_t)(timegm(tm) - t);
350
+ log->day_start = 0;
351
+ log->day_end = 0;
352
+ *log->day_buf = '\0';
353
+ log->thread = 0;
354
+
355
+ if (ERR_OK != configure(err, log, cfg)) {
356
+ return err->code;
357
+ }
358
+ if ('\0' != *log->dir) {
359
+ if (0 != mkdir(log->dir, 0770) && EEXIST != errno) {
360
+ return err_no(err, "Failed to create '%s'.", log->dir);
361
+ }
362
+ if (ERR_OK != open_log_file(err, log)) {
363
+ return err->code;
364
+ }
365
+ }
366
+ log->q = (LogEntry)malloc(sizeof(struct _LogEntry) * qsize);
367
+ log->end = log->q + qsize;
368
+
369
+ memset(log->q, 0, sizeof(struct _LogEntry) * qsize);
370
+ log->head = log->q;
371
+ log->tail = log->q + 1;
372
+ atomic_flag_clear(&log->push_lock);
373
+ log->wait_state = NOT_WAITING;
374
+ // Create when/if needed.
375
+ log->rsock = 0;
376
+ log->wsock = 0;
377
+
378
+ pthread_create(&log->thread, NULL, loop, log);
379
+
380
+ return ERR_OK;
381
+ }
382
+
383
+ void
384
+ log_close(Log log) {
385
+ log->done = true;
386
+ // TBD wake up loop like push does
387
+ log_cat_on(log, NULL, false);
388
+ if (0 != log->thread) {
389
+ pthread_join(log->thread, NULL);
390
+ log->thread = 0;
391
+ }
392
+ if (NULL != log->file) {
393
+ fclose(log->file);
394
+ log->file = NULL;
395
+ }
396
+ free(log->q);
397
+ log->q = NULL;
398
+ log->end = NULL;
399
+ if (0 < log->wsock) {
400
+ close(log->wsock);
401
+ }
402
+ if (0 < log->rsock) {
403
+ close(log->rsock);
404
+ }
405
+ }
406
+
407
+ void
408
+ log_cat_reg(Log log, LogCat cat, const char *label, LogLevel level, const char *color, bool on) {
409
+ cat->log = log;
410
+ strncpy(cat->label, label, sizeof(cat->label));
411
+ cat->label[sizeof(cat->label) - 1] = '\0';
412
+ cat->level = level;
413
+ cat->color = find_color(color);
414
+ cat->on = on;
415
+ cat->next = log->cats;
416
+ log->cats = cat;
417
+ }
418
+
419
+ void
420
+ log_cat_on(Log log, const char *label, bool on) {
421
+ LogCat cat;
422
+
423
+ for (cat = log->cats; NULL != cat; cat = cat->next) {
424
+ if (NULL == label || 0 == strcasecmp(label, cat->label)) {
425
+ cat->on = on;
426
+ break;
427
+ }
428
+ }
429
+ }
430
+
431
+ LogCat
432
+ log_cat_find(Log log, const char *label) {
433
+ LogCat cat;
434
+
435
+ for (cat = log->cats; NULL != cat; cat = cat->next) {
436
+ if (0 == strcasecmp(label, cat->label)) {
437
+ return cat;
438
+ }
439
+ }
440
+ return NULL;
441
+ }
442
+
443
+ void
444
+ log_catv(LogCat cat, const char *fmt, va_list ap) {
445
+ if (cat->on && !cat->log->done) {
446
+ Log log = cat->log;
447
+ struct timespec ts;
448
+ LogEntry e;
449
+ LogEntry tail;
450
+ int cnt;
451
+ va_list ap2;
452
+
453
+ va_copy(ap2, ap);
454
+
455
+ while (atomic_flag_test_and_set(&log->push_lock)) {
456
+ dsleep(RETRY_SECS);
457
+ }
458
+ // Wait for head to move on.
459
+ while (atomic_load(&log->head) == log->tail) {
460
+ dsleep(RETRY_SECS);
461
+ }
462
+ // TBD fill in the entry at tail
463
+ clock_gettime(CLOCK_REALTIME, &ts);
464
+ e = log->tail;
465
+ e->cat = cat;
466
+ e->when = (int64_t)ts.tv_sec * 1000000000LL + (int64_t)ts.tv_nsec;
467
+ e->whatp = NULL;
468
+ if ((int)sizeof(e->what) <= (cnt = vsnprintf(e->what, sizeof(e->what), fmt, ap))) {
469
+ e->whatp = (char*)malloc(cnt + 1);
470
+
471
+ if (NULL != e->whatp) {
472
+ vsnprintf(e->whatp, cnt + 1, fmt, ap2);
473
+ }
474
+ }
475
+ tail = log->tail + 1;
476
+ if (log->end <= tail) {
477
+ tail = log->q;
478
+ }
479
+ atomic_store(&log->tail, tail);
480
+ atomic_flag_clear(&log->push_lock);
481
+ va_end(ap2);
482
+
483
+ if (0 != log->wsock && WAITING == atomic_load(&log->wait_state)) {
484
+ if (write(log->wsock, ".", 1)) {}
485
+ atomic_store(&log->wait_state, NOTIFIED);
486
+ }
487
+ }
488
+ }
489
+
490
+ void
491
+ log_cat(LogCat cat, const char *fmt, ...) {
492
+ va_list ap;
493
+
494
+ va_start(ap, fmt);
495
+ log_catv(cat, fmt, ap);
496
+ va_end(ap);
497
+ }