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.
- checksums.yaml +4 -4
- data/README.md +11 -9
- data/ext/agoo/agoo.c +45 -0
- data/ext/agoo/base64.c +107 -0
- data/ext/agoo/base64.h +15 -0
- data/ext/agoo/ccache.c +301 -0
- data/ext/agoo/ccache.h +53 -0
- data/ext/agoo/con.c +522 -82
- data/ext/agoo/con.h +7 -5
- data/ext/agoo/debug.c +121 -7
- data/ext/agoo/debug.h +11 -6
- data/ext/agoo/error_stream.c +5 -6
- data/ext/agoo/error_stream.h +1 -1
- data/ext/agoo/extconf.rb +2 -1
- data/ext/agoo/hook.c +4 -4
- data/ext/agoo/hook.h +1 -0
- data/ext/agoo/http.c +2 -2
- data/ext/agoo/http.h +2 -0
- data/ext/agoo/log.c +604 -219
- data/ext/agoo/log.h +20 -7
- data/ext/agoo/page.c +20 -23
- data/ext/agoo/page.h +2 -0
- data/ext/agoo/pub.c +111 -0
- data/ext/agoo/pub.h +40 -0
- data/ext/agoo/queue.c +2 -2
- data/ext/agoo/rack_logger.c +15 -71
- data/ext/agoo/rack_logger.h +1 -1
- data/ext/agoo/request.c +96 -21
- data/ext/agoo/request.h +23 -12
- data/ext/agoo/res.c +5 -2
- data/ext/agoo/res.h +4 -0
- data/ext/agoo/response.c +13 -12
- data/ext/agoo/response.h +1 -2
- data/ext/agoo/server.c +290 -428
- data/ext/agoo/server.h +10 -10
- data/ext/agoo/sha1.c +148 -0
- data/ext/agoo/sha1.h +10 -0
- data/ext/agoo/sse.c +26 -0
- data/ext/agoo/sse.h +12 -0
- data/ext/agoo/sub.c +111 -0
- data/ext/agoo/sub.h +36 -0
- data/ext/agoo/subscription.c +54 -0
- data/ext/agoo/subscription.h +18 -0
- data/ext/agoo/text.c +26 -4
- data/ext/agoo/text.h +2 -0
- data/ext/agoo/types.h +13 -0
- data/ext/agoo/upgraded.c +148 -0
- data/ext/agoo/upgraded.h +13 -0
- data/ext/agoo/websocket.c +248 -0
- data/ext/agoo/websocket.h +27 -0
- data/lib/agoo/version.rb +1 -1
- data/lib/rack/handler/agoo.rb +13 -6
- data/test/base_handler_test.rb +24 -22
- data/test/log_test.rb +146 -199
- data/test/rack_handler_test.rb +19 -20
- data/test/static_test.rb +30 -28
- metadata +23 -7
- data/test/rrr/test.rb +0 -26
- data/test/tests.rb +0 -8
data/ext/agoo/con.h
CHANGED
@@ -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
|
-
|
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
|
-
//
|
37
|
+
struct _CSlot *slot; // only set for push connections
|
36
38
|
} *Con;
|
37
39
|
|
38
|
-
extern Con con_create(Err err,
|
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
|
|
data/ext/agoo/debug.c
CHANGED
@@ -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
|
-
|
38
|
-
|
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
|
+
}
|
data/ext/agoo/debug.h
CHANGED
@@ -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
|
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__ */
|
data/ext/agoo/error_stream.c
CHANGED
@@ -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(
|
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(&
|
77
|
+
log_cat(&error_cat, "%s", es->text->text);
|
79
78
|
es->text->len = 0;
|
80
79
|
|
81
80
|
return self;
|
data/ext/agoo/error_stream.h
CHANGED
data/ext/agoo/extconf.rb
CHANGED
@@ -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
|
-
|
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
|
|
data/ext/agoo/hook.c
CHANGED
@@ -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
|
-
|
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
|
}
|
data/ext/agoo/hook.h
CHANGED
data/ext/agoo/http.c
CHANGED
@@ -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;
|
data/ext/agoo/http.h
CHANGED
data/ext/agoo/log.c
CHANGED
@@ -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(
|
62
|
-
LogEntry head = atomic_load(&
|
75
|
+
log_queue_empty() {
|
76
|
+
LogEntry head = atomic_load(&the_log.head);
|
63
77
|
LogEntry next = head + 1;
|
64
78
|
|
65
|
-
if (
|
66
|
-
next =
|
79
|
+
if (the_log.end <= next) {
|
80
|
+
next = the_log.q;
|
67
81
|
}
|
68
|
-
if (!head->ready &&
|
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(
|
76
|
-
LogEntry e =
|
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 =
|
83
|
-
if (
|
84
|
-
next =
|
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(&
|
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(&
|
108
|
+
atomic_store(&the_log.head, next);
|
95
109
|
|
96
|
-
return
|
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(
|
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 +=
|
123
|
-
if (
|
124
|
-
t -=
|
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(
|
135
|
-
|
136
|
-
|
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 (
|
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,
|
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,
|
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(
|
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(
|
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 (
|
176
|
-
snprintf(path, sizeof(path), "%s/%s",
|
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
|
-
|
184
|
-
|
197
|
+
void
|
198
|
+
log_rotate() {
|
185
199
|
char from[1024];
|
186
200
|
char to[1024];
|
187
201
|
|
188
|
-
if (NULL !=
|
189
|
-
fclose(
|
190
|
-
|
202
|
+
if (NULL != the_log.file) {
|
203
|
+
fclose(the_log.file);
|
204
|
+
the_log.file = NULL;
|
191
205
|
}
|
192
|
-
for (int seq =
|
193
|
-
snprintf(to, sizeof(to), log_format,
|
194
|
-
snprintf(from, sizeof(from), log_format,
|
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,
|
198
|
-
snprintf(from, sizeof(from), "%s/%s",
|
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
|
-
|
202
|
-
|
215
|
+
the_log.file = fopen(from, "w");
|
216
|
+
the_log.size = 0;
|
203
217
|
|
204
|
-
remove_old_logs(
|
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 (!
|
215
|
-
if (NULL != (e = log_queue_pop(
|
216
|
-
if (
|
217
|
-
if (
|
218
|
-
classic_write(
|
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 !=
|
224
|
-
if (
|
225
|
-
|
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
|
-
|
238
|
+
the_log.size += jwrite(e, the_log.file);
|
228
239
|
}
|
229
|
-
if (
|
230
|
-
|
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(
|
255
|
+
log_flush(double timeout) {
|
245
256
|
timeout += dtime();
|
246
257
|
|
247
|
-
while (!
|
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 !=
|
254
|
-
fflush(
|
264
|
+
if (NULL != the_log.file) {
|
265
|
+
fflush(the_log.file);
|
255
266
|
}
|
256
267
|
return true;
|
257
268
|
}
|
258
269
|
|
259
|
-
static
|
260
|
-
|
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",
|
274
|
+
snprintf(path, sizeof(path), "%s/%s", the_log.dir, log_name);
|
323
275
|
|
324
|
-
|
325
|
-
if (NULL ==
|
326
|
-
|
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
|
-
|
329
|
-
if (
|
330
|
-
|
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(
|
389
|
-
|
287
|
+
log_close() {
|
288
|
+
the_log.done = true;
|
390
289
|
// TBD wake up loop like push does
|
391
|
-
log_cat_on(
|
392
|
-
if (0 !=
|
393
|
-
pthread_join(
|
394
|
-
|
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 !=
|
397
|
-
fclose(
|
398
|
-
|
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(
|
402
|
-
|
403
|
-
|
404
|
-
if (0 <
|
405
|
-
close(
|
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 <
|
408
|
-
close(
|
306
|
+
if (0 < the_log.rsock) {
|
307
|
+
close(the_log.rsock);
|
409
308
|
}
|
410
309
|
}
|
411
310
|
|
412
311
|
void
|
413
|
-
log_cat_reg(
|
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 =
|
421
|
-
|
318
|
+
cat->next = the_log.cats;
|
319
|
+
the_log.cats = cat;
|
422
320
|
}
|
423
321
|
|
424
322
|
void
|
425
|
-
log_cat_on(
|
323
|
+
log_cat_on(const char *label, bool on) {
|
426
324
|
LogCat cat;
|
427
325
|
|
428
|
-
for (cat =
|
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(
|
335
|
+
log_cat_find(const char *label) {
|
438
336
|
LogCat cat;
|
439
337
|
|
440
|
-
for (cat =
|
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 && !
|
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(&
|
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(&
|
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 =
|
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 =
|
483
|
-
if (
|
484
|
-
tail =
|
379
|
+
tail = the_log.tail + 1;
|
380
|
+
if (the_log.end <= tail) {
|
381
|
+
tail = the_log.q;
|
485
382
|
}
|
486
|
-
atomic_store(&
|
487
|
-
atomic_flag_clear(&
|
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 !=
|
491
|
-
if (write(
|
492
|
-
atomic_store(&
|
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
|
+
}
|