agoo 1.2.2 → 2.0.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.

Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -9
  3. data/ext/agoo/agoo.c +45 -0
  4. data/ext/agoo/base64.c +107 -0
  5. data/ext/agoo/base64.h +15 -0
  6. data/ext/agoo/ccache.c +301 -0
  7. data/ext/agoo/ccache.h +53 -0
  8. data/ext/agoo/con.c +522 -82
  9. data/ext/agoo/con.h +7 -5
  10. data/ext/agoo/debug.c +121 -7
  11. data/ext/agoo/debug.h +11 -6
  12. data/ext/agoo/error_stream.c +5 -6
  13. data/ext/agoo/error_stream.h +1 -1
  14. data/ext/agoo/extconf.rb +2 -1
  15. data/ext/agoo/hook.c +4 -4
  16. data/ext/agoo/hook.h +1 -0
  17. data/ext/agoo/http.c +2 -2
  18. data/ext/agoo/http.h +2 -0
  19. data/ext/agoo/log.c +604 -219
  20. data/ext/agoo/log.h +20 -7
  21. data/ext/agoo/page.c +20 -23
  22. data/ext/agoo/page.h +2 -0
  23. data/ext/agoo/pub.c +111 -0
  24. data/ext/agoo/pub.h +40 -0
  25. data/ext/agoo/queue.c +2 -2
  26. data/ext/agoo/rack_logger.c +15 -71
  27. data/ext/agoo/rack_logger.h +1 -1
  28. data/ext/agoo/request.c +96 -21
  29. data/ext/agoo/request.h +23 -12
  30. data/ext/agoo/res.c +5 -2
  31. data/ext/agoo/res.h +4 -0
  32. data/ext/agoo/response.c +13 -12
  33. data/ext/agoo/response.h +1 -2
  34. data/ext/agoo/server.c +290 -428
  35. data/ext/agoo/server.h +10 -10
  36. data/ext/agoo/sha1.c +148 -0
  37. data/ext/agoo/sha1.h +10 -0
  38. data/ext/agoo/sse.c +26 -0
  39. data/ext/agoo/sse.h +12 -0
  40. data/ext/agoo/sub.c +111 -0
  41. data/ext/agoo/sub.h +36 -0
  42. data/ext/agoo/subscription.c +54 -0
  43. data/ext/agoo/subscription.h +18 -0
  44. data/ext/agoo/text.c +26 -4
  45. data/ext/agoo/text.h +2 -0
  46. data/ext/agoo/types.h +13 -0
  47. data/ext/agoo/upgraded.c +148 -0
  48. data/ext/agoo/upgraded.h +13 -0
  49. data/ext/agoo/websocket.c +248 -0
  50. data/ext/agoo/websocket.h +27 -0
  51. data/lib/agoo/version.rb +1 -1
  52. data/lib/rack/handler/agoo.rb +13 -6
  53. data/test/base_handler_test.rb +24 -22
  54. data/test/log_test.rb +146 -199
  55. data/test/rack_handler_test.rb +19 -20
  56. data/test/static_test.rb +30 -28
  57. metadata +23 -7
  58. data/test/rrr/test.rb +0 -26
  59. data/test/tests.rb +0 -8
@@ -11,31 +11,33 @@
11
11
  #include "request.h"
12
12
  #include "response.h"
13
13
  #include "server.h"
14
+ #include "types.h"
14
15
 
15
16
  #define MAX_HEADER_SIZE 8192
16
17
 
18
+ struct _CSlot;
19
+
17
20
  typedef struct _Con {
18
21
  int sock;
22
+ ConKind kind;
19
23
  struct pollfd *pp;
20
- //char id[32];
21
- uint64_t iid;
24
+ uint64_t id;
22
25
  char buf[MAX_HEADER_SIZE];
23
26
  size_t bcnt;
24
27
 
25
28
  ssize_t mcnt; // how much has been read so far
26
29
  ssize_t wcnt; // how much has been written
27
30
 
28
- Server server;
29
31
  double timeout;
30
32
  bool closing;
31
33
  Req req;
32
34
  Res res_head;
33
35
  Res res_tail;
34
36
 
35
- //FEval eval;
37
+ struct _CSlot *slot; // only set for push connections
36
38
  } *Con;
37
39
 
38
- extern Con con_create(Err err, Server server, int sock, uint64_t id);
40
+ extern Con con_create(Err err, int sock, uint64_t id);
39
41
  extern void con_destroy(Con c);
40
42
  extern const char* con_header_value(const char *header, int hlen, const char *key, int *vlen);
41
43
 
@@ -1,12 +1,28 @@
1
1
  // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
2
 
3
+ #include <pthread.h>
4
+ #include <stdbool.h>
3
5
  #include <stdio.h>
4
6
 
5
7
  #include <ruby.h>
8
+ #include <ruby/thread.h>
6
9
 
7
10
  #include "debug.h"
8
11
 
12
+ typedef struct _Rec {
13
+ struct _Rec *next;
14
+ void *ptr;
15
+ const char *type;
16
+ const char *file;
17
+ int line;
18
+ } *Rec;
19
+
20
+ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
21
+ static Rec recs = NULL;
22
+
23
+ atomic_int mem_cb = 0;
9
24
  atomic_int mem_con = 0;
25
+ atomic_int mem_cslot = 0;
10
26
  atomic_int mem_err_stream = 0;
11
27
  atomic_int mem_eval_threads = 0;
12
28
  atomic_int mem_header = 0;
@@ -20,6 +36,7 @@ atomic_int mem_page = 0;
20
36
  atomic_int mem_page_msg = 0;
21
37
  atomic_int mem_page_path = 0;
22
38
  atomic_int mem_page_slot = 0;
39
+ atomic_int mem_pub = 0;
23
40
  atomic_int mem_qitem = 0;
24
41
  atomic_int mem_queue_item = 0;
25
42
  atomic_int mem_rack_logger = 0;
@@ -27,19 +44,49 @@ atomic_int mem_req = 0;
27
44
  atomic_int mem_res = 0;
28
45
  atomic_int mem_res_body = 0;
29
46
  atomic_int mem_response = 0;
30
- atomic_int mem_server = 0;
31
47
  atomic_int mem_text = 0;
32
48
  atomic_int mem_to_s = 0;
33
49
 
34
- void
35
- debug_print_stats() {
36
50
  #ifdef MEM_DEBUG
37
- rb_gc_enable();
38
- rb_gc();
39
-
51
+ static void
52
+ print_stats() {
53
+ Rec r;
54
+
55
+ printf("********************************************************************************\n");
56
+ pthread_mutex_lock(&lock);
57
+
58
+ while (NULL != (r = recs)) {
59
+ int cnt = 1;
60
+ Rec prev = NULL;
61
+ Rec next = NULL;
62
+ Rec r2;
63
+
64
+ recs = r->next;
65
+ for (r2 = recs; NULL != r2; r2 = next) {
66
+ next = r2->next;
67
+ if (r->file == r2->file && r->line == r2->line) {
68
+ cnt++;
69
+ if (NULL == prev) {
70
+ recs = r2->next;
71
+ } else {
72
+ prev->next = r2->next;
73
+ }
74
+ free(r2);
75
+ } else {
76
+ prev = r2;
77
+ }
78
+ }
79
+ printf("%16s:%3d %-12s allocated and not freed %4d times.\n", r->file, r->line, r->type + 4, cnt);
80
+ free(r);
81
+ }
82
+ pthread_mutex_unlock(&lock);
83
+
40
84
  printf("********************************************************************************\n");
85
+ #if 0
41
86
  printf("memory statistics\n");
87
+ printf(" mem_cb: %d\n", mem_cb);
42
88
  printf(" mem_con: %d\n", mem_con);
89
+ printf(" mem_cslot: %d\n", mem_cslot);
43
90
  printf(" mem_err_stream: %d\n", mem_err_stream);
44
91
  printf(" mem_eval_threads: %d\n", mem_eval_threads);
45
92
  printf(" mem_header: %d\n", mem_header);
@@ -53,6 +100,7 @@ debug_print_stats() {
53
100
  printf(" mem_page_msg: %d\n", mem_page_msg);
54
101
  printf(" mem_page_path: %d\n", mem_page_path);
55
102
  printf(" mem_page_slot: %d\n", mem_page_slot);
103
+ printf(" mem_pub: %d\n", mem_pub);
56
104
  printf(" mem_qitem: %d\n", mem_qitem);
57
105
  printf(" mem_queue_item: %d\n", mem_queue_item);
58
106
  printf(" mem_rack_logger: %d\n", mem_rack_logger);
@@ -60,8 +108,74 @@ debug_print_stats() {
60
108
  printf(" mem_res: %d\n", mem_res);
61
109
  printf(" mem_res_body: %d\n", mem_res_body);
62
110
  printf(" mem_response: %d\n", mem_response);
63
- printf(" mem_server: %d\n", mem_server);
64
111
  printf(" mem_text: %d\n", mem_text);
65
112
  printf(" mem_to_s: %d\n", mem_to_s);
66
113
  #endif
67
114
  }
115
+ #endif
116
+
117
+ #if 0
118
+ static void*
119
+ handle_gc(void *x) {
120
+ rb_gc_enable();
121
+ rb_gc();
122
+ return NULL;
123
+ }
124
+ #endif
125
+
126
+ void
127
+ debug_report() {
128
+ #ifdef MEM_DEBUG
129
+ // TBD how can ownership of GVL be determined?
130
+ //rb_thread_call_with_gvl(handle_gc, NULL);
131
+ print_stats();
132
+ #endif
133
+ }
134
+
135
+ void
136
+ debug_rreport() {
137
+ #ifdef MEM_DEBUG
138
+ rb_gc_enable();
139
+ rb_gc();
140
+ print_stats();
141
+ #endif
142
+ }
143
+
144
+ void
145
+ debug_add(void *ptr, const char *type, const char *file, int line) {
146
+ Rec r = (Rec)malloc(sizeof(struct _Rec));
147
+
148
+ r->ptr = ptr;
149
+ r->type = type;
150
+ r->file = file;
151
+ r->line = line;
152
+ pthread_mutex_lock(&lock);
153
+ r->next = recs;
154
+ recs = r;
155
+ pthread_mutex_unlock(&lock);
156
+ }
157
+
158
+ void
159
+ debug_del(void *ptr, const char *file, int line) {
160
+ Rec r = NULL;
161
+ Rec prev = NULL;
162
+
163
+ pthread_mutex_lock(&lock);
164
+ for (r = recs; NULL != r; r = r->next) {
165
+ if (ptr == r->ptr) {
166
+ if (NULL == prev) {
167
+ recs = r->next;
168
+ } else {
169
+ prev->next = r->next;
170
+ }
171
+ break;
172
+ }
173
+ prev = r;
174
+ }
175
+ pthread_mutex_unlock(&lock);
176
+ if (NULL == r) {
177
+ printf("Free at %s:%d (%p) not allocated or already freed.\n", file, line, ptr);
178
+ } else {
179
+ free(r);
180
+ }
181
+ }
@@ -6,14 +6,16 @@
6
6
  #include <stdatomic.h>
7
7
 
8
8
  #ifdef MEM_DEBUG
9
- #define DEBUG_ALLOC(var) { atomic_fetch_add(&var, 1); }
10
- #define DEBUG_FREE(var) { atomic_fetch_sub(&var, 1); }
9
+ #define DEBUG_ALLOC(var, ptr) { atomic_fetch_add(&var, 1); debug_add(ptr, #var, __FILE__, __LINE__); }
10
+ #define DEBUG_FREE(var, ptr) { atomic_fetch_sub(&var, 1); debug_del(ptr, __FILE__, __LINE__); }
11
11
  #else
12
- #define DEBUG_ALLOC(var) { }
13
- #define DEBUG_FREE(var) { }
12
+ #define DEBUG_ALLOC(var, ptr) { }
13
+ #define DEBUG_FREE(var, ptr) { }
14
14
  #endif
15
15
 
16
+ extern atomic_int mem_cb;
16
17
  extern atomic_int mem_con;
18
+ extern atomic_int mem_cslot;
17
19
  extern atomic_int mem_err_stream;
18
20
  extern atomic_int mem_eval_threads;
19
21
  extern atomic_int mem_header;
@@ -27,6 +29,7 @@ extern atomic_int mem_page;
27
29
  extern atomic_int mem_page_msg;
28
30
  extern atomic_int mem_page_path;
29
31
  extern atomic_int mem_page_slot;
32
+ extern atomic_int mem_pub;
30
33
  extern atomic_int mem_qitem;
31
34
  extern atomic_int mem_queue_item;
32
35
  extern atomic_int mem_rack_logger;
@@ -34,10 +37,12 @@ extern atomic_int mem_req;
34
37
  extern atomic_int mem_res;
35
38
  extern atomic_int mem_res_body;
36
39
  extern atomic_int mem_response;
37
- extern atomic_int mem_server;
38
40
  extern atomic_int mem_text;
39
41
  extern atomic_int mem_to_s;
40
42
 
41
- extern void debug_print_stats();
43
+ extern void debug_add(void *ptr, const char *type, const char *file, int line);
44
+ extern void debug_del(void *ptr, const char *file, int line);
45
+ extern void debug_report();
46
+ extern void debug_rreport(); // when called from ruby
42
47
 
43
48
  #endif /* __AGOO_DEBUG_H__ */
@@ -15,19 +15,18 @@ static void
15
15
  es_free(void *ptr) {
16
16
  ErrorStream es = (ErrorStream)ptr;
17
17
 
18
- DEBUG_FREE(mem_err_stream)
19
- DEBUG_FREE(mem_text)
18
+ DEBUG_FREE(mem_err_stream, ptr);
19
+ DEBUG_FREE(mem_text, es->text)
20
20
  free(es->text); // allocated with malloc
21
21
  xfree(ptr);
22
22
  }
23
23
 
24
24
  VALUE
25
- error_stream_new(Server server) {
25
+ error_stream_new() {
26
26
  ErrorStream es = ALLOC(struct _ErrorStream);
27
27
 
28
- DEBUG_ALLOC(mem_err_stream)
28
+ DEBUG_ALLOC(mem_err_stream, es)
29
29
  es->text = text_allocate(1024);
30
- es->server = server;
31
30
 
32
31
  return Data_Wrap_Struct(es_class, NULL, es_free, es);
33
32
  }
@@ -75,7 +74,7 @@ static VALUE
75
74
  es_flush(VALUE self) {
76
75
  ErrorStream es = (ErrorStream)DATA_PTR(self);
77
76
 
78
- log_cat(&es->server->error_cat, "%s", es->text->text);
77
+ log_cat(&error_cat, "%s", es->text->text);
79
78
  es->text->len = 0;
80
79
 
81
80
  return self;
@@ -8,6 +8,6 @@
8
8
  #include "server.h"
9
9
 
10
10
  extern void error_stream_init(VALUE mod);
11
- extern VALUE error_stream_new(Server server);
11
+ extern VALUE error_stream_new();
12
12
 
13
13
  #endif // __AGOO_ERROR_STREAM_H__
@@ -5,7 +5,8 @@ extension_name = 'agoo'
5
5
  dir_config(extension_name)
6
6
 
7
7
  $CPPFLAGS += " -DPLATFORM_LINUX" if 'x86_64-linux' == RUBY_PLATFORM
8
- RbConfig::MAKEFILE_CONFIG['CC'] = "gcc-6" if 'x86_64-linux' == RUBY_PLATFORM
8
+ # Travis defaults to an older version of gcc to force a newer version.
9
+ RbConfig::MAKEFILE_CONFIG['CC'] = "gcc-7" if 'x86_64-linux' == RUBY_PLATFORM
9
10
 
10
11
  create_makefile(File.join(extension_name, extension_name))
11
12
 
@@ -11,7 +11,7 @@ hook_create(Method method, const char *pattern, VALUE handler) {
11
11
  Hook hook = (Hook)malloc(sizeof(struct _Hook));
12
12
 
13
13
  if (NULL != hook) {
14
- DEBUG_ALLOC(mem_hook)
14
+ DEBUG_ALLOC(mem_hook, hook)
15
15
  if (NULL == pattern) {
16
16
  pattern = "";
17
17
  }
@@ -19,7 +19,7 @@ hook_create(Method method, const char *pattern, VALUE handler) {
19
19
  hook->handler = handler;
20
20
  rb_gc_register_address(&handler);
21
21
  hook->pattern = strdup(pattern);
22
- DEBUG_ALLOC(mem_hook_pattern)
22
+ DEBUG_ALLOC(mem_hook_pattern, hook->pattern)
23
23
  hook->method = method;
24
24
  if (rb_respond_to(handler, rb_intern("on_request"))) {
25
25
  hook->type = BASE_HOOK;
@@ -39,8 +39,8 @@ hook_create(Method method, const char *pattern, VALUE handler) {
39
39
 
40
40
  void
41
41
  hook_destroy(Hook hook) {
42
- DEBUG_FREE(mem_hook_pattern)
43
- DEBUG_FREE(mem_hook)
42
+ DEBUG_FREE(mem_hook_pattern, hook->pattern)
43
+ DEBUG_FREE(mem_hook, hook)
44
44
  free(hook->pattern);
45
45
  free(hook);
46
46
  }
@@ -14,6 +14,7 @@ typedef enum {
14
14
  RACK_HOOK = 'R',
15
15
  BASE_HOOK = 'B',
16
16
  WAB_HOOK = 'W',
17
+ PUSH_HOOK = 'P',
17
18
  } HookType;
18
19
 
19
20
  typedef struct _Hook {
@@ -471,7 +471,7 @@ key_set(const char *key) {
471
471
  Slot s;
472
472
 
473
473
  if (NULL != (s = (Slot)malloc(sizeof(struct _Slot)))) {
474
- DEBUG_ALLOC(mem_http_slot)
474
+ DEBUG_ALLOC(mem_http_slot, s)
475
475
  s->hash = h;
476
476
  s->klen = len;
477
477
  s->key = key;
@@ -499,7 +499,7 @@ http_cleanup() {
499
499
  for (int i = BUCKET_SIZE; 0 < i; i--, sp++) {
500
500
  for (s = *sp; NULL != s; s = n) {
501
501
  n = s->next;
502
- DEBUG_FREE(mem_http_slot)
502
+ DEBUG_FREE(mem_http_slot, s)
503
503
  free(s);
504
504
  }
505
505
  *sp = NULL;
@@ -6,6 +6,8 @@
6
6
  #include <stdbool.h>
7
7
 
8
8
  extern void http_init();
9
+ extern void http_cleanup();
10
+
9
11
  extern void http_header_ok(const char *key, int klen, const char *value, int vlen);
10
12
 
11
13
  extern const char* http_code_message(int code);
@@ -45,6 +45,20 @@ static struct _Color colors[] = {
45
45
 
46
46
  static const char level_chars[] = { 'F', 'E', 'W', 'I', 'D', '?' };
47
47
 
48
+ static VALUE log_mod = Qundef;
49
+
50
+ struct _Log the_log = {NULL};
51
+ struct _LogCat fatal_cat;
52
+ struct _LogCat error_cat;
53
+ struct _LogCat warn_cat;
54
+ struct _LogCat info_cat;
55
+ struct _LogCat debug_cat;
56
+ struct _LogCat con_cat;
57
+ struct _LogCat req_cat;
58
+ struct _LogCat resp_cat;
59
+ struct _LogCat eval_cat;
60
+ struct _LogCat push_cat;
61
+
48
62
  static Color
49
63
  find_color(const char *name) {
50
64
  if (NULL != name) {
@@ -58,42 +72,42 @@ find_color(const char *name) {
58
72
  }
59
73
 
60
74
  static bool
61
- log_queue_empty(Log log) {
62
- LogEntry head = atomic_load(&log->head);
75
+ log_queue_empty() {
76
+ LogEntry head = atomic_load(&the_log.head);
63
77
  LogEntry next = head + 1;
64
78
 
65
- if (log->end <= next) {
66
- next = log->q;
79
+ if (the_log.end <= next) {
80
+ next = the_log.q;
67
81
  }
68
- if (!head->ready && log->tail == next) {
82
+ if (!head->ready && the_log.tail == next) {
69
83
  return true;
70
84
  }
71
85
  return false;
72
86
  }
73
87
 
74
88
  static LogEntry
75
- log_queue_pop(Log log, double timeout) {
76
- LogEntry e = log->head;
89
+ log_queue_pop(double timeout) {
90
+ LogEntry e = the_log.head;
77
91
  LogEntry next;
78
92
 
79
93
  if (e->ready) {
80
94
  return e;
81
95
  }
82
- next = log->head + 1;
83
- if (log->end <= next) {
84
- next = log->q;
96
+ next = the_log.head + 1;
97
+ if (the_log.end <= next) {
98
+ next = the_log.q;
85
99
  }
86
100
  // If the next is the tail then wait for something to be appended.
87
- for (int cnt = (int)(timeout / RETRY_SECS); atomic_load(&log->tail) == next; cnt--) {
101
+ for (int cnt = (int)(timeout / RETRY_SECS); atomic_load(&the_log.tail) == next; cnt--) {
88
102
  // TBD poll would be better
89
103
  if (cnt <= 0) {
90
104
  return NULL;
91
105
  }
92
106
  dsleep(RETRY_SECS);
93
107
  }
94
- atomic_store(&log->head, next);
108
+ atomic_store(&the_log.head, next);
95
109
 
96
- return log->head;
110
+ return the_log.head;
97
111
  }
98
112
 
99
113
 
@@ -110,7 +124,7 @@ jwrite(LogEntry e, FILE *file) {
110
124
 
111
125
  //I 2015/05/23 11:22:33.123456789 label: The contents of the what field.
112
126
  static int
113
- classic_write(Log log, LogEntry e, FILE *file) {
127
+ classic_write(LogEntry e, FILE *file) {
114
128
  time_t t = (time_t)(e->when / 1000000000LL);
115
129
  int hour = 0;
116
130
  int min = 0;
@@ -119,9 +133,9 @@ classic_write(Log log, LogEntry e, FILE *file) {
119
133
  char levelc = level_chars[e->cat->level];
120
134
  int cnt = 0;
121
135
 
122
- t += log->zone;
123
- if (log->day_start <= t && t < log->day_end) {
124
- t -= log->day_start;
136
+ t += the_log.zone;
137
+ if (the_log.day_start <= t && t < the_log.day_end) {
138
+ t -= the_log.day_start;
125
139
  hour = (int)(t / 3600);
126
140
  min = t % 3600 / 60;
127
141
  sec = t % 60;
@@ -131,19 +145,19 @@ classic_write(Log log, LogEntry e, FILE *file) {
131
145
  hour = tm->tm_hour;
132
146
  min = tm->tm_min;
133
147
  sec = tm->tm_sec;
134
- sprintf(log->day_buf, "%04d/%02d/%02d ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
135
- log->day_start = t - (hour * 3600 + min * 60 + sec);
136
- log->day_end = log->day_start + 86400;
148
+ sprintf(the_log.day_buf, "%04d/%02d/%02d ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
149
+ the_log.day_start = t - (hour * 3600 + min * 60 + sec);
150
+ the_log.day_end = the_log.day_start + 86400;
137
151
  }
138
- if (log->colorize) {
152
+ if (the_log.colorize) {
139
153
  cnt = fprintf(file, "%s%c %s%02d:%02d:%02d.%09lld %s: %s%s\n",
140
- e->cat->color->ansi, levelc, log->day_buf, hour, min, sec, frac,
154
+ e->cat->color->ansi, levelc, the_log.day_buf, hour, min, sec, frac,
141
155
  e->cat->label,
142
156
  (NULL == e->whatp ? e->what : e->whatp),
143
157
  RESET_COLOR);
144
158
  } else {
145
159
  cnt += fprintf(file, "%c %s%02d:%02d:%02d.%09lld %s: %s\n",
146
- levelc, log->day_buf, hour, min, sec, frac,
160
+ levelc, the_log.day_buf, hour, min, sec, frac,
147
161
  e->cat->label,
148
162
  (NULL == e->whatp ? e->what : e->whatp));
149
163
  }
@@ -153,12 +167,12 @@ classic_write(Log log, LogEntry e, FILE *file) {
153
167
  // Remove all file with sequence numbers higher than max_files. max_files is
154
168
  // max number of archived version. It does not include the primary.
155
169
  static void
156
- remove_old_logs(Log log) {
170
+ remove_old_logs() {
157
171
  struct dirent *de;
158
172
  long seq;
159
173
  char *end;
160
174
  char path[1024];
161
- DIR *dir = opendir(log->dir);
175
+ DIR *dir = opendir(the_log.dir);
162
176
 
163
177
  while (NULL != (de = readdir(dir))) {
164
178
  if ('.' == *de->d_name || '\0' == *de->d_name) {
@@ -172,67 +186,64 @@ remove_old_logs(Log log) {
172
186
  continue;
173
187
  }
174
188
  seq = strtol(de->d_name + sizeof(log_prefix) - 1, &end, 10);
175
- if (log->max_files < seq) {
176
- snprintf(path, sizeof(path), "%s/%s", log->dir, de->d_name);
189
+ if (the_log.max_files < seq) {
190
+ snprintf(path, sizeof(path), "%s/%s", the_log.dir, de->d_name);
177
191
  remove(path);
178
192
  }
179
193
  }
180
194
  closedir(dir);
181
195
  }
182
196
 
183
- static int
184
- rotate(Err err, Log log) {
197
+ void
198
+ log_rotate() {
185
199
  char from[1024];
186
200
  char to[1024];
187
201
 
188
- if (NULL != log->file) {
189
- fclose(log->file);
190
- log->file = NULL;
202
+ if (NULL != the_log.file) {
203
+ fclose(the_log.file);
204
+ the_log.file = NULL;
191
205
  }
192
- for (int seq = log->max_files; 0 < seq; seq--) {
193
- snprintf(to, sizeof(to), log_format, log->dir, seq + 1);
194
- snprintf(from, sizeof(from), log_format, log->dir, seq);
206
+ for (int seq = the_log.max_files; 0 < seq; seq--) {
207
+ snprintf(to, sizeof(to), log_format, the_log.dir, seq + 1);
208
+ snprintf(from, sizeof(from), log_format, the_log.dir, seq);
195
209
  rename(from, to);
196
210
  }
197
- snprintf(to, sizeof(to), log_format, log->dir, 1);
198
- snprintf(from, sizeof(from), "%s/%s", log->dir, log_name);
211
+ snprintf(to, sizeof(to), log_format, the_log.dir, 1);
212
+ snprintf(from, sizeof(from), "%s/%s", the_log.dir, log_name);
199
213
  rename(from, to);
200
214
 
201
- log->file = fopen(from, "w");
202
- log->size = 0;
215
+ the_log.file = fopen(from, "w");
216
+ the_log.size = 0;
203
217
 
204
- remove_old_logs(log);
205
-
206
- return ERR_OK;
218
+ remove_old_logs();
207
219
  }
208
220
 
209
221
  static void*
210
222
  loop(void *ctx) {
211
- Log log = (Log)ctx;
212
223
  LogEntry e;
213
224
 
214
- while (!log->done || !log_queue_empty(log)) {
215
- if (NULL != (e = log_queue_pop(log, 0.5))) {
216
- if (log->console) {
217
- if (log->classic) {
218
- classic_write(log, e, stdout);
225
+ while (!the_log.done || !log_queue_empty()) {
226
+ if (NULL != (e = log_queue_pop(0.5))) {
227
+ if (the_log.console) {
228
+ if (the_log.classic) {
229
+ classic_write(e, stdout);
219
230
  } else {
220
231
  jwrite(e, stdout);
221
232
  }
222
233
  }
223
- if (NULL != log->file) {
224
- if (log->classic) {
225
- log->size += classic_write(log, e, log->file);
234
+ if (NULL != the_log.file) {
235
+ if (the_log.classic) {
236
+ the_log.size += classic_write(e, the_log.file);
226
237
  } else {
227
- log->size += jwrite(e, log->file);
238
+ the_log.size += jwrite(e, the_log.file);
228
239
  }
229
- if (log->max_size <= log->size) {
230
- rotate(NULL, log);
240
+ if (the_log.max_size <= the_log.size) {
241
+ log_rotate();
231
242
  }
232
243
  }
233
244
  if (NULL != e->whatp) {
234
245
  free(e->whatp);
235
- DEBUG_FREE(mem_log_what)
246
+ DEBUG_FREE(mem_log_what, e->whatp)
236
247
  }
237
248
  e->ready = false;
238
249
  }
@@ -241,191 +252,78 @@ loop(void *ctx) {
241
252
  }
242
253
 
243
254
  bool
244
- log_flush(Log log, double timeout) {
255
+ log_flush(double timeout) {
245
256
  timeout += dtime();
246
257
 
247
- while (!log->done && !log_queue_empty(log)) {
258
+ while (!the_log.done && !log_queue_empty()) {
248
259
  if (timeout < dtime()) {
249
260
  return false;
250
261
  }
251
262
  dsleep(0.001);
252
263
  }
253
- if (NULL != log->file) {
254
- fflush(log->file);
264
+ if (NULL != the_log.file) {
265
+ fflush(the_log.file);
255
266
  }
256
267
  return true;
257
268
  }
258
269
 
259
- static int
260
- configure(Err err, Log log, VALUE options) {
261
- if (Qnil != options) {
262
- VALUE v;
263
-
264
- if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_dir"))))) {
265
- rb_check_type(v, T_STRING);
266
- strncpy(log->dir, StringValuePtr(v), sizeof(log->dir));
267
- log->dir[sizeof(log->dir) - 1] = '\0';
268
- }
269
- if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_max_files"))))) {
270
- int max = FIX2INT(v);
271
-
272
- if (1 <= max || max < 100) {
273
- log->max_files = max;
274
- } else {
275
- rb_raise(rb_eArgError, "log_max_files must be between 1 and 100.");
276
- }
277
- }
278
- if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_max_size"))))) {
279
- int max = FIX2INT(v);
280
-
281
- if (1 <= max) {
282
- log->max_size = max;
283
- } else {
284
- rb_raise(rb_eArgError, "log_max_size must be 1 or more.");
285
- }
286
- }
287
- if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_console"))))) {
288
- log->console = (Qtrue == v);
289
- }
290
- if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_classic"))))) {
291
- log->classic = (Qtrue == v);
292
- }
293
- if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_colorize"))))) {
294
- log->colorize = (Qtrue == v);
295
- }
296
- if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("log_states"))))) {
297
- if (T_HASH == rb_type(v)) {
298
- LogCat cat = log->cats;
299
- VALUE cv;
300
-
301
- for (; NULL != cat; cat = cat->next) {
302
- if (Qnil != (cv = rb_hash_lookup(v, ID2SYM(rb_intern(cat->label))))) {
303
- if (Qtrue == cv) {
304
- cat->on = true;
305
- } else if (Qfalse == cv) {
306
- cat->on = false;
307
- }
308
- }
309
- }
310
- } else {
311
- rb_raise(rb_eArgError, "log_states must be a Hash.");
312
- }
313
- }
314
- }
315
- return ERR_OK;
316
- }
317
-
318
- static int
319
- open_log_file(Err err, Log log) {
270
+ static void
271
+ open_log_file() {
320
272
  char path[1024];
321
273
 
322
- snprintf(path, sizeof(path), "%s/%s", log->dir, log_name);
274
+ snprintf(path, sizeof(path), "%s/%s", the_log.dir, log_name);
323
275
 
324
- log->file = fopen(path, "a");
325
- if (NULL == log->file) {
326
- return err_no(err, "failed to open '%s'.", path);
276
+ the_log.file = fopen(path, "a");
277
+ if (NULL == the_log.file) {
278
+ rb_raise(rb_eIOError, "Failed to create '%s'.", path);
327
279
  }
328
- log->size = ftell(log->file);
329
- if (log->max_size <= log->size) {
330
- return rotate(err, log);
280
+ the_log.size = ftell(the_log.file);
281
+ if (the_log.max_size <= the_log.size) {
282
+ log_rotate();
331
283
  }
332
- return ERR_OK;
333
- }
334
-
335
- int
336
- log_init(Err err, Log log, VALUE cfg) {
337
- time_t t = time(NULL);
338
- struct tm *tm = localtime(&t);
339
- int qsize = 1024;
340
-
341
- //log->cats = NULL; done outside of here
342
- *log->dir = '\0';
343
- log->file = NULL;
344
- log->max_files = 3;
345
- log->max_size = 100000000; // 100M
346
- log->size = 0;
347
- log->done = false;
348
- log->console = true;
349
- log->classic = true;
350
- log->colorize = true;
351
- log->zone = (int)(timegm(tm) - t);
352
- log->day_start = 0;
353
- log->day_end = 0;
354
- *log->day_buf = '\0';
355
- log->thread = 0;
356
-
357
- if (ERR_OK != configure(err, log, cfg)) {
358
- return err->code;
359
- }
360
- if ('\0' != *log->dir) {
361
- if (0 != mkdir(log->dir, 0770) && EEXIST != errno) {
362
- return err_no(err, "Failed to create '%s'.", log->dir);
363
- }
364
- if (ERR_OK != open_log_file(err, log)) {
365
- return err->code;
366
- }
367
- }
368
- log->q = (LogEntry)malloc(sizeof(struct _LogEntry) * qsize);
369
- DEBUG_ALLOC(mem_log_entry)
370
-
371
- log->end = log->q + qsize;
372
-
373
- memset(log->q, 0, sizeof(struct _LogEntry) * qsize);
374
- log->head = log->q;
375
- log->tail = log->q + 1;
376
- atomic_flag_clear(&log->push_lock);
377
- log->wait_state = NOT_WAITING;
378
- // Create when/if needed.
379
- log->rsock = 0;
380
- log->wsock = 0;
381
-
382
- pthread_create(&log->thread, NULL, loop, log);
383
-
384
- return ERR_OK;
385
284
  }
386
285
 
387
286
  void
388
- log_close(Log log) {
389
- log->done = true;
287
+ log_close() {
288
+ the_log.done = true;
390
289
  // TBD wake up loop like push does
391
- log_cat_on(log, NULL, false);
392
- if (0 != log->thread) {
393
- pthread_join(log->thread, NULL);
394
- log->thread = 0;
290
+ log_cat_on(NULL, false);
291
+ if (0 != the_log.thread) {
292
+ pthread_join(the_log.thread, NULL);
293
+ the_log.thread = 0;
395
294
  }
396
- if (NULL != log->file) {
397
- fclose(log->file);
398
- log->file = NULL;
295
+ if (NULL != the_log.file) {
296
+ fclose(the_log.file);
297
+ the_log.file = NULL;
399
298
  }
400
- DEBUG_FREE(mem_log_entry)
401
- free(log->q);
402
- log->q = NULL;
403
- log->end = NULL;
404
- if (0 < log->wsock) {
405
- close(log->wsock);
299
+ DEBUG_FREE(mem_log_entry, the_log.q)
300
+ free(the_log.q);
301
+ the_log.q = NULL;
302
+ the_log.end = NULL;
303
+ if (0 < the_log.wsock) {
304
+ close(the_log.wsock);
406
305
  }
407
- if (0 < log->rsock) {
408
- close(log->rsock);
306
+ if (0 < the_log.rsock) {
307
+ close(the_log.rsock);
409
308
  }
410
309
  }
411
310
 
412
311
  void
413
- log_cat_reg(Log log, LogCat cat, const char *label, LogLevel level, const char *color, bool on) {
414
- cat->log = log;
312
+ log_cat_reg(LogCat cat, const char *label, LogLevel level, const char *color, bool on) {
415
313
  strncpy(cat->label, label, sizeof(cat->label));
416
314
  cat->label[sizeof(cat->label) - 1] = '\0';
417
315
  cat->level = level;
418
316
  cat->color = find_color(color);
419
317
  cat->on = on;
420
- cat->next = log->cats;
421
- log->cats = cat;
318
+ cat->next = the_log.cats;
319
+ the_log.cats = cat;
422
320
  }
423
321
 
424
322
  void
425
- log_cat_on(Log log, const char *label, bool on) {
323
+ log_cat_on(const char *label, bool on) {
426
324
  LogCat cat;
427
325
 
428
- for (cat = log->cats; NULL != cat; cat = cat->next) {
326
+ for (cat = the_log.cats; NULL != cat; cat = cat->next) {
429
327
  if (NULL == label || 0 == strcasecmp(label, cat->label)) {
430
328
  cat->on = on;
431
329
  break;
@@ -434,10 +332,10 @@ log_cat_on(Log log, const char *label, bool on) {
434
332
  }
435
333
 
436
334
  LogCat
437
- log_cat_find(Log log, const char *label) {
335
+ log_cat_find(const char *label) {
438
336
  LogCat cat;
439
337
 
440
- for (cat = log->cats; NULL != cat; cat = cat->next) {
338
+ for (cat = the_log.cats; NULL != cat; cat = cat->next) {
441
339
  if (0 == strcasecmp(label, cat->label)) {
442
340
  return cat;
443
341
  }
@@ -447,8 +345,7 @@ log_cat_find(Log log, const char *label) {
447
345
 
448
346
  void
449
347
  log_catv(LogCat cat, const char *fmt, va_list ap) {
450
- if (cat->on && !cat->log->done) {
451
- Log log = cat->log;
348
+ if (cat->on && !the_log.done) {
452
349
  struct timespec ts;
453
350
  LogEntry e;
454
351
  LogEntry tail;
@@ -457,39 +354,39 @@ log_catv(LogCat cat, const char *fmt, va_list ap) {
457
354
 
458
355
  va_copy(ap2, ap);
459
356
 
460
- while (atomic_flag_test_and_set(&log->push_lock)) {
357
+ while (atomic_flag_test_and_set(&the_log.push_lock)) {
461
358
  dsleep(RETRY_SECS);
462
359
  }
463
360
  // Wait for head to move on.
464
- while (atomic_load(&log->head) == log->tail) {
361
+ while (atomic_load(&the_log.head) == the_log.tail) {
465
362
  dsleep(RETRY_SECS);
466
363
  }
467
364
  // TBD fill in the entry at tail
468
365
  clock_gettime(CLOCK_REALTIME, &ts);
469
- e = log->tail;
366
+ e = the_log.tail;
470
367
  e->cat = cat;
471
368
  e->when = (int64_t)ts.tv_sec * 1000000000LL + (int64_t)ts.tv_nsec;
472
369
  e->whatp = NULL;
473
370
  if ((int)sizeof(e->what) <= (cnt = vsnprintf(e->what, sizeof(e->what), fmt, ap))) {
474
371
  e->whatp = (char*)malloc(cnt + 1);
475
372
 
476
- DEBUG_ALLOC(mem_log_what)
373
+ DEBUG_ALLOC(mem_log_what, e->whatp)
477
374
 
478
375
  if (NULL != e->whatp) {
479
376
  vsnprintf(e->whatp, cnt + 1, fmt, ap2);
480
377
  }
481
378
  }
482
- tail = log->tail + 1;
483
- if (log->end <= tail) {
484
- tail = log->q;
379
+ tail = the_log.tail + 1;
380
+ if (the_log.end <= tail) {
381
+ tail = the_log.q;
485
382
  }
486
- atomic_store(&log->tail, tail);
487
- atomic_flag_clear(&log->push_lock);
383
+ atomic_store(&the_log.tail, tail);
384
+ atomic_flag_clear(&the_log.push_lock);
488
385
  va_end(ap2);
489
386
 
490
- if (0 != log->wsock && WAITING == atomic_load(&log->wait_state)) {
491
- if (write(log->wsock, ".", 1)) {}
492
- atomic_store(&log->wait_state, NOTIFIED);
387
+ if (0 != the_log.wsock && WAITING == atomic_load(&the_log.wait_state)) {
388
+ if (write(the_log.wsock, ".", 1)) {}
389
+ atomic_store(&the_log.wait_state, NOTIFIED);
493
390
  }
494
391
  }
495
392
  }
@@ -502,3 +399,491 @@ log_cat(LogCat cat, const char *fmt, ...) {
502
399
  log_catv(cat, fmt, ap);
503
400
  va_end(ap);
504
401
  }
402
+
403
+ /* Document-method: configure
404
+ *
405
+ * call-seq: configure(options)
406
+ *
407
+ * Configures the logger
408
+ *
409
+ * - *options* [_Hash_] server options
410
+ *
411
+ * - *:dir* [_String_] directory to place log files in. If nil or empty then no log files are written.
412
+ *
413
+ * - *:console* [_true_|_false_] if true log entry are display on the console.
414
+ *
415
+ * - *:classic* [_true_|_false_] if true log entry follow a classic format. If false log entries are JSON.
416
+ *
417
+ * - *:colorize* [_true_|_false_] if true log entries are colorized.
418
+ *
419
+ * - *:states* [_Hash_] a map of logging categories and whether they should be on or off. Categories are:
420
+ * - *:ERROR* errors
421
+ * - *:WARN* warnings
422
+ * - *:INFO* infomational
423
+ * - *:DEBUG* debugging
424
+ * - *:connect* openning and closing of connections
425
+ * - *:request* requests
426
+ * - *:response* responses
427
+ * - *:eval* handler evaluationss
428
+ * - *:push* writes to WebSocket or SSE connection
429
+ */
430
+ static VALUE
431
+ rlog_configure(VALUE self, VALUE options) {
432
+ if (Qnil != options) {
433
+ VALUE v;
434
+
435
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("dir"))))) {
436
+ rb_check_type(v, T_STRING);
437
+ strncpy(the_log.dir, StringValuePtr(v), sizeof(the_log.dir));
438
+ the_log.dir[sizeof(the_log.dir) - 1] = '\0';
439
+ }
440
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("max_files"))))) {
441
+ int max = FIX2INT(v);
442
+
443
+ if (1 <= max || max < 100) {
444
+ the_log.max_files = max;
445
+ } else {
446
+ rb_raise(rb_eArgError, "max_files must be between 1 and 100.");
447
+ }
448
+ }
449
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("max_size"))))) {
450
+ int max = FIX2INT(v);
451
+
452
+ if (1 <= max) {
453
+ the_log.max_size = max;
454
+ } else {
455
+ rb_raise(rb_eArgError, "max_size must be 1 or more.");
456
+ }
457
+ }
458
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("console"))))) {
459
+ the_log.console = (Qtrue == v);
460
+ }
461
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("classic"))))) {
462
+ the_log.classic = (Qtrue == v);
463
+ }
464
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("colorize"))))) {
465
+ the_log.colorize = (Qtrue == v);
466
+ }
467
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("states"))))) {
468
+ if (T_HASH == rb_type(v)) {
469
+ LogCat cat = the_log.cats;
470
+ VALUE cv;
471
+
472
+ for (; NULL != cat; cat = cat->next) {
473
+ if (Qnil != (cv = rb_hash_lookup(v, ID2SYM(rb_intern(cat->label))))) {
474
+ if (Qtrue == cv) {
475
+ cat->on = true;
476
+ } else if (Qfalse == cv) {
477
+ cat->on = false;
478
+ }
479
+ }
480
+ }
481
+ } else {
482
+ rb_raise(rb_eArgError, "states must be a Hash.");
483
+ }
484
+ }
485
+ }
486
+ if (NULL != the_log.file) {
487
+ fclose(the_log.file);
488
+ the_log.file = NULL;
489
+ }
490
+ if ('\0' != *the_log.dir) {
491
+ if (0 != mkdir(the_log.dir, 0770) && EEXIST != errno) {
492
+ rb_raise(rb_eIOError, "Failed to create '%s'.", the_log.dir);
493
+ }
494
+ open_log_file();
495
+ }
496
+ return Qnil;
497
+ }
498
+
499
+ /* Document-method: shutdown
500
+ *
501
+ * call-seq: shutdown()
502
+ *
503
+ * Shutdown the logger. Writes are flushed before shutting down.
504
+ */
505
+ static VALUE
506
+ rlog_shutdown(VALUE self) {
507
+ log_close();
508
+ return Qnil;
509
+ }
510
+
511
+ /* Document-method: error?
512
+ *
513
+ * call-seq: error?()
514
+ *
515
+ * Returns true is errors are being logged.
516
+ */
517
+ static VALUE
518
+ rlog_errorp(VALUE self) {
519
+ return error_cat.on ? Qtrue : Qfalse;
520
+ }
521
+
522
+ /* Document-method: warn?
523
+ *
524
+ * call-seq: warn?()
525
+ *
526
+ * Returns true is warnings are being logged.
527
+ */
528
+ static VALUE
529
+ rlog_warnp(VALUE self) {
530
+ return warn_cat.on ? Qtrue : Qfalse;
531
+ }
532
+
533
+ /* Document-method: info?
534
+ *
535
+ * call-seq: info?()
536
+ *
537
+ * Returns true is info entries are being logged.
538
+ */
539
+ static VALUE
540
+ rlog_infop(VALUE self) {
541
+ return info_cat.on ? Qtrue : Qfalse;
542
+ }
543
+
544
+ /* Document-method: debug?
545
+ *
546
+ * call-seq: debug?()
547
+ *
548
+ * Returns true is debug entries are being logged.
549
+ */
550
+ static VALUE
551
+ rlog_debugp(VALUE self) {
552
+ return debug_cat.on ? Qtrue : Qfalse;
553
+ }
554
+
555
+ /* Document-method: error
556
+ *
557
+ * call-seq: error(msg)
558
+ *
559
+ * Log an error message.
560
+ */
561
+ static VALUE
562
+ rlog_error(VALUE self, VALUE msg) {
563
+ log_cat(&error_cat, "%s", StringValuePtr(msg));
564
+ return Qnil;
565
+ }
566
+
567
+ /* Document-method: warn
568
+ *
569
+ * call-seq: warn(msg)
570
+ *
571
+ * Log a warn message.
572
+ */
573
+ static VALUE
574
+ rlog_warn(VALUE self, VALUE msg) {
575
+ log_cat(&warn_cat, "%s", StringValuePtr(msg));
576
+ return Qnil;
577
+ }
578
+
579
+ /* Document-method: info
580
+ *
581
+ * call-seq: info(msg)
582
+ *
583
+ * Log an info message.
584
+ */
585
+ static VALUE
586
+ rlog_info(VALUE self, VALUE msg) {
587
+ log_cat(&info_cat, "%s", StringValuePtr(msg));
588
+ return Qnil;
589
+ }
590
+
591
+ /* Document-method: debug
592
+ *
593
+ * call-seq: debug(msg)
594
+ *
595
+ * Log a debug message.
596
+ */
597
+ static VALUE
598
+ rlog_debug(VALUE self, VALUE msg) {
599
+ log_cat(&debug_cat, "%s", StringValuePtr(msg));
600
+ return Qnil;
601
+ }
602
+
603
+ /* Document-method: color
604
+ *
605
+ * call-seq: color(label)
606
+ *
607
+ * Returns the current color name as a Symbol for the specified label.
608
+ */
609
+ static VALUE
610
+ rlog_color_get(VALUE self, VALUE label) {
611
+ LogCat cat = log_cat_find(StringValuePtr(label));
612
+
613
+ if (NULL == cat) {
614
+ return Qnil;
615
+ }
616
+ return ID2SYM(rb_intern(cat->color->name));
617
+ }
618
+
619
+ /* Document-method: set_color
620
+ *
621
+ * call-seq: set_color(label, color_symbol)
622
+ *
623
+ * Sets color of the category associated with a label. Valid colors are
624
+ * :black, :red, :green, :yellow, :blue, :magenta, :cyan, :white, :gray,
625
+ * :dark_red, :dark_green, :brown, :dark_blue, :purple, and :dark_cyan.
626
+ */
627
+ static VALUE
628
+ rlog_color_set(VALUE self, VALUE label, VALUE color) {
629
+ const char *label_str = StringValuePtr(label);
630
+ const char *color_name = StringValuePtr(color);
631
+ LogCat cat = log_cat_find(label_str);
632
+ Color c = find_color(color_name);
633
+
634
+ if (NULL == cat) {
635
+ rb_raise(rb_eArgError, "%s is not a valid category.", label_str);
636
+ }
637
+ if (NULL == c) {
638
+ rb_raise(rb_eArgError, "%s is not a valid color.", color_name);
639
+ }
640
+ cat->color = c;
641
+
642
+ return Qnil;
643
+ }
644
+
645
+ /* Document-method: state
646
+ *
647
+ * call-seq: state(label)
648
+ *
649
+ * Returns the current state of the category identified by the specified
650
+ * label.
651
+ */
652
+ static VALUE
653
+ rlog_on_get(VALUE self, VALUE label) {
654
+ LogCat cat = log_cat_find(StringValuePtr(label));
655
+
656
+ if (NULL == cat) {
657
+ return Qfalse;
658
+ }
659
+ return cat->on ? Qtrue : Qfalse;
660
+ }
661
+
662
+ /* Document-method: set_state
663
+ *
664
+ * call-seq: set_state(label, state)
665
+ *
666
+ * Sets state of the category associated with a label.
667
+ */
668
+ static VALUE
669
+ rlog_on_set(VALUE self, VALUE label, VALUE state) {
670
+ const char *label_str = StringValuePtr(label);
671
+ LogCat cat = log_cat_find(label_str);
672
+
673
+ if (NULL == cat) {
674
+ rb_raise(rb_eArgError, "%s is not a valid category.", label_str);
675
+ }
676
+ cat->on = (Qtrue == state);
677
+
678
+ return cat->on ? Qtrue : Qfalse;
679
+ }
680
+
681
+ /* Document-method: log
682
+ *
683
+ * call-seq: log[label] = msg
684
+ *
685
+ * Log a message in the specified category.
686
+ */
687
+ static VALUE
688
+ rlog_log(VALUE self, VALUE label, VALUE msg) {
689
+ const char *label_str = StringValuePtr(label);
690
+ LogCat cat = log_cat_find(label_str);
691
+
692
+ if (NULL == cat) {
693
+ rb_raise(rb_eArgError, "%s is not a valid category.", label_str);
694
+ }
695
+ log_cat(cat, "%s", StringValuePtr(msg));
696
+
697
+ return Qnil;
698
+ }
699
+
700
+ /* Document-method: flush
701
+ *
702
+ * call-seq: flush
703
+ *
704
+ * Flush the log queue and write all entries to disk or the console. The call
705
+ * waits for the flush to complete or the timeout to be exceeded.
706
+ */
707
+ static VALUE
708
+ rlog_flush(VALUE self, VALUE to) {
709
+ double timeout = NUM2DBL(to);
710
+
711
+ if (!log_flush(timeout)) {
712
+ rb_raise(rb_eStandardError, "timed out waiting for log flush.");
713
+ }
714
+ return Qnil;
715
+ }
716
+
717
+ /* Document-method: rotate
718
+ *
719
+ * call-seq: rotate()
720
+ *
721
+ * Rotate the log files.
722
+ */
723
+ static VALUE
724
+ rlog_rotate(VALUE self) {
725
+ log_rotate();
726
+ return Qnil;
727
+ }
728
+
729
+ /* Document-method: console=
730
+ *
731
+ * call-seq: console=(on)
732
+ *
733
+ * If on then log output also goes to the console.
734
+ */
735
+ static VALUE
736
+ rlog_console(VALUE self, VALUE on) {
737
+ the_log.console = (Qtrue == on);
738
+ return Qnil;
739
+ }
740
+
741
+ /* Document-method: classic
742
+ *
743
+ * call-seq: classic()
744
+ *
745
+ * Set the log format to classic format.
746
+ */
747
+ static VALUE
748
+ rlog_classic(VALUE self) {
749
+ the_log.classic = true;
750
+ return Qnil;
751
+ }
752
+
753
+ /* Document-method: json
754
+ *
755
+ * call-seq: json()
756
+ *
757
+ * Set the log format to JSON format.
758
+ */
759
+ static VALUE
760
+ rlog_json(VALUE self) {
761
+ the_log.classic = false;
762
+ return Qnil;
763
+ }
764
+
765
+ /* Document-method: max_size
766
+ *
767
+ * call-seq: max_size(size)
768
+ *
769
+ * Maximum log files size is reset.
770
+ */
771
+ static VALUE
772
+ rlog_max_size(VALUE self, VALUE rmax) {
773
+ int max = FIX2INT(rmax);
774
+
775
+ if (1 <= max) {
776
+ the_log.max_size = max;
777
+ } else {
778
+ rb_raise(rb_eArgError, "max_size must be 1 or more.");
779
+ }
780
+ return Qnil;
781
+ }
782
+
783
+ /* Document-method: max_files
784
+ *
785
+ * call-seq: max_files(max)
786
+ *
787
+ * Maximum log files files is reset.
788
+ */
789
+ static VALUE
790
+ rlog_max_files(VALUE self, VALUE rmax) {
791
+ int max = FIX2INT(rmax);
792
+
793
+ if (1 <= max || max < 100) {
794
+ the_log.max_files = max;
795
+ } else {
796
+ rb_raise(rb_eArgError, "max_files must be between 1 and 100.");
797
+ }
798
+ return Qnil;
799
+ }
800
+
801
+
802
+ /* Document-class: Agoo::Log
803
+ *
804
+ * An asynchronous and thread safe logger that includes file rollover and
805
+ * multiple logging categories. It is a feature based logger with a level
806
+ * overlay.
807
+ */
808
+ void
809
+ log_init(VALUE mod) {
810
+ time_t t = time(NULL);
811
+ struct tm *tm = localtime(&t);
812
+ int qsize = 1024;
813
+
814
+ log_mod = rb_define_module_under(mod, "Log");
815
+
816
+ rb_define_module_function(log_mod, "configure", rlog_configure, 1);
817
+ rb_define_module_function(log_mod, "shutdown", rlog_shutdown, 0);
818
+
819
+ rb_define_module_function(log_mod, "error?", rlog_errorp, 0);
820
+ rb_define_module_function(log_mod, "warn?", rlog_warnp, 0);
821
+ rb_define_module_function(log_mod, "info?", rlog_infop, 0);
822
+ rb_define_module_function(log_mod, "debug?", rlog_debugp, 0);
823
+
824
+ rb_define_module_function(log_mod, "error", rlog_error, 1);
825
+ rb_define_module_function(log_mod, "warn", rlog_warn, 1);
826
+ rb_define_module_function(log_mod, "info", rlog_info, 1);
827
+ rb_define_module_function(log_mod, "debug", rlog_debug, 1);
828
+
829
+ // TBD maybe in a future version
830
+ //rb_define_module_function(log_mod, "register", rlog_register, 2);
831
+
832
+ rb_define_module_function(log_mod, "color", rlog_color_get, 1);
833
+ rb_define_module_function(log_mod, "set_color", rlog_color_set, 2);
834
+
835
+ rb_define_module_function(log_mod, "state", rlog_on_get, 1);
836
+ rb_define_module_function(log_mod, "set_state", rlog_on_set, 2);
837
+
838
+ rb_define_module_function(log_mod, "log", rlog_log, 2);
839
+
840
+ rb_define_module_function(log_mod, "flush", rlog_flush, 1);
841
+ rb_define_module_function(log_mod, "rotate", rlog_rotate, 0);
842
+ rb_define_module_function(log_mod, "console=", rlog_console, 1);
843
+ rb_define_module_function(log_mod, "classic", rlog_classic, 0);
844
+ rb_define_module_function(log_mod, "json", rlog_json, 0);
845
+ rb_define_module_function(log_mod, "max_size=", rlog_max_size, 1);
846
+ rb_define_module_function(log_mod, "max_files=", rlog_max_files, 1);
847
+
848
+ the_log.cats = NULL;
849
+ *the_log.dir = '\0';
850
+ the_log.file = NULL;
851
+ the_log.max_files = 3;
852
+ the_log.max_size = 100000000; // 100M
853
+ the_log.size = 0;
854
+ the_log.done = false;
855
+ the_log.console = true;
856
+ the_log.classic = true;
857
+ the_log.colorize = true;
858
+ the_log.zone = (int)(timegm(tm) - t);
859
+ the_log.day_start = 0;
860
+ the_log.day_end = 0;
861
+ *the_log.day_buf = '\0';
862
+ the_log.thread = 0;
863
+
864
+ the_log.q = (LogEntry)malloc(sizeof(struct _LogEntry) * qsize);
865
+ DEBUG_ALLOC(mem_log_entry, the_log.q)
866
+ the_log.end = the_log.q + qsize;
867
+ memset(the_log.q, 0, sizeof(struct _LogEntry) * qsize);
868
+ the_log.head = the_log.q;
869
+ the_log.tail = the_log.q + 1;
870
+
871
+ atomic_flag_clear(&the_log.push_lock);
872
+ the_log.wait_state = NOT_WAITING;
873
+ // Create when/if needed.
874
+ the_log.rsock = 0;
875
+ the_log.wsock = 0;
876
+
877
+ log_cat_reg(&fatal_cat, "FATAL", FATAL, RED, true);
878
+ log_cat_reg(&error_cat, "ERROR", ERROR, RED, true);
879
+ log_cat_reg(&warn_cat, "WARN", WARN, YELLOW, true);
880
+ log_cat_reg(&info_cat, "INFO", INFO, GREEN, true);
881
+ log_cat_reg(&debug_cat, "DEBUG", DEBUG, GRAY, false);
882
+ log_cat_reg(&con_cat, "connect", INFO, GREEN, false);
883
+ log_cat_reg(&req_cat, "request", INFO, CYAN, false);
884
+ log_cat_reg(&resp_cat, "response", INFO, DARK_CYAN, false);
885
+ log_cat_reg(&eval_cat, "eval", INFO, BLUE, false);
886
+ log_cat_reg(&push_cat, "push", INFO, DARK_CYAN, false);
887
+
888
+ pthread_create(&the_log.thread, NULL, loop, log);
889
+ }