jlog 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +64 -0
- data/Rakefile +22 -0
- data/ext/jlog/extconf.rb +5 -0
- data/ext/jlog/jlog.c +590 -0
- data/jlog.gemspec +25 -0
- data/lib/jlog.rb +5 -0
- data/lib/jlog/version.rb +3 -0
- data/spec/jlog/reader_spec.rb +66 -0
- data/spec/jlog/writer_spec.rb +21 -0
- data/spec/jlog_spec.rb +42 -0
- data/spec/spec_helper.rb +13 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2c76ce9ecf9b6d897f60f7905da4f4a7f0d7fac3
|
4
|
+
data.tar.gz: 885cdc3455c73676a3e2c72af8edccb8fca615fc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e4577db9d3d9bd21d4f0a841143820aadb56871729ae91180ff340127b0718f13afec51d52d0f94d55b3a6dc9dbf568080e6b8abbc0a4c4c9646e918fe50bd5a
|
7
|
+
data.tar.gz: 2912450e7590c18c5416f53c5abfba9935e9456955dc0db47b06313d9f2ad89ddbf6081ae69d2e0ff17b72858ee6f8e3d135048c917bb7bef7aa10a985a9237c
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Ezekiel Templin
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# JLog
|
2
|
+
|
3
|
+
Ruby C-bindings for OmniTI Labs' [Jlog](https://github.com/omniti-labs/jlog). Founded upon [work](https://github.com/mbainter/ruby-jlog/) by Mark Bainter (@mbainter.)
|
4
|
+
|
5
|
+
> JLog is short for "journaled log" and this package is really an API and implementation that is libjlog. What is libjlog? libjlog is a pure C, very simple durable message queue with multiple subscribers and publishers (both thread and multi-process safe). The basic concept is that publishers can open a log and write messages to it while subscribers open the log and consume messages from it. "That sounds easy." libjlog abstracts away the need to perform log rotation or maintenance by publishing into fixed size log buffers and eliminating old log buffers when there are no more consumers pending.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'jlog'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install jlog
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
log = Jlog.new('/var/log/logname')
|
25
|
+
log.add_subscriber 'LogSubscriber'
|
26
|
+
log.close
|
27
|
+
|
28
|
+
writer = Jlog::Writer.new('/var/log/logname')
|
29
|
+
|
30
|
+
writer.open
|
31
|
+
writer.write 'This is the first log message'
|
32
|
+
writer.write 'This is the second log message'
|
33
|
+
writer.write "This is the third log message, created at #{Time.now}"
|
34
|
+
writer.close
|
35
|
+
|
36
|
+
reader = Jlog::Reader.new '/var/log/logname'
|
37
|
+
reader.open 'LogSubscriber'
|
38
|
+
|
39
|
+
first = reader.read
|
40
|
+
second = reader.read
|
41
|
+
reader.rewind
|
42
|
+
|
43
|
+
if reader.read == second
|
44
|
+
puts "Rewind sets log position to last checkpoint."
|
45
|
+
end
|
46
|
+
|
47
|
+
reader.checkpoint
|
48
|
+
|
49
|
+
third = reader.read
|
50
|
+
reader.rewind
|
51
|
+
third_full = reader.read_message
|
52
|
+
|
53
|
+
if third == third_msg[:message]
|
54
|
+
ts = third_msg[:timestamp]
|
55
|
+
puts "#{third} and logged #{Time.at(ts)} (or #{ts} seconds since epoch)"
|
56
|
+
end
|
57
|
+
|
58
|
+
reader.checkpoint
|
59
|
+
reader.close
|
60
|
+
```
|
61
|
+
|
62
|
+
## Requirements
|
63
|
+
|
64
|
+
* [Jlog](https://github.com/omniti-labs/jlog)
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/extensiontask"
|
3
|
+
|
4
|
+
spec = Gem::Specification.new("jlog.gemspec")
|
5
|
+
|
6
|
+
task :test => [:clobber, :compile] do
|
7
|
+
$LOAD_PATH.unshift('lib', 'spec')
|
8
|
+
Dir.glob('./spec/**/*_spec.rb') { |f| require f }
|
9
|
+
end
|
10
|
+
|
11
|
+
task :default => :test
|
12
|
+
task :spec => :test
|
13
|
+
|
14
|
+
Rake::ExtensionTask.new do |ext|
|
15
|
+
ext.name = "jlog"
|
16
|
+
ext.ext_dir = "ext/jlog"
|
17
|
+
ext.lib_dir = "lib/jlog"
|
18
|
+
ext.tmp_dir = "tmp"
|
19
|
+
ext.source_pattern = "*.c"
|
20
|
+
ext.config_options << '-Wall'
|
21
|
+
ext.gem_spec = spec
|
22
|
+
end
|
data/ext/jlog/extconf.rb
ADDED
data/ext/jlog/jlog.c
ADDED
@@ -0,0 +1,590 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <jlog.h>
|
3
|
+
#include <fcntl.h>
|
4
|
+
#include <stdio.h>
|
5
|
+
#include <sys/time.h>
|
6
|
+
|
7
|
+
typedef struct {
|
8
|
+
jlog_ctx *ctx;
|
9
|
+
char *path;
|
10
|
+
jlog_id start;
|
11
|
+
jlog_id last;
|
12
|
+
jlog_id prev;
|
13
|
+
jlog_id end;
|
14
|
+
int auto_checkpoint;
|
15
|
+
int error;
|
16
|
+
} jlog_obj;
|
17
|
+
|
18
|
+
typedef jlog_obj * Jlog;
|
19
|
+
typedef jlog_obj * Jlog_Writer;
|
20
|
+
typedef jlog_obj * Jlog_Reader;
|
21
|
+
|
22
|
+
VALUE cJlog;
|
23
|
+
VALUE cJlogWriter;
|
24
|
+
VALUE cJlogReader;
|
25
|
+
VALUE eJlog;
|
26
|
+
|
27
|
+
static VALUE message_sym;
|
28
|
+
static VALUE timestamp_sym;
|
29
|
+
|
30
|
+
void rJlog_populate_subscribers(VALUE);
|
31
|
+
|
32
|
+
void rJlog_mark(Jlog jo) { }
|
33
|
+
|
34
|
+
void rJlog_free(Jlog jo) {
|
35
|
+
if(jo->ctx) {
|
36
|
+
jlog_ctx_close(jo->ctx);
|
37
|
+
}
|
38
|
+
|
39
|
+
if(jo->path) {
|
40
|
+
xfree(jo->path);
|
41
|
+
}
|
42
|
+
|
43
|
+
if(jo) {
|
44
|
+
xfree(jo);
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
void rJlog_raise(Jlog jo, const char* mess)
|
49
|
+
{
|
50
|
+
VALUE e = rb_exc_new2(eJlog, mess);
|
51
|
+
rb_iv_set(e, "error", INT2FIX(jlog_ctx_err(jo->ctx)));
|
52
|
+
rb_iv_set(e, "errstr", rb_str_new2(jlog_ctx_err_string(jo->ctx)));
|
53
|
+
rb_iv_set(e, "errno", INT2FIX(jlog_ctx_errno(jo->ctx)));
|
54
|
+
|
55
|
+
rb_raise(eJlog, "%s: %d %s", mess, jlog_ctx_err(jo->ctx), jlog_ctx_err_string(jo->ctx));
|
56
|
+
}
|
57
|
+
|
58
|
+
VALUE rJlog_initialize(int argc, VALUE* argv, VALUE klass)
|
59
|
+
{
|
60
|
+
int options;
|
61
|
+
Jlog jo;
|
62
|
+
jlog_id zeroed = {0,0};
|
63
|
+
VALUE path;
|
64
|
+
VALUE optarg;
|
65
|
+
VALUE size;
|
66
|
+
|
67
|
+
rb_scan_args(argc, argv, "12", &path, &optarg, &size);
|
68
|
+
|
69
|
+
if(NIL_P(optarg)) {
|
70
|
+
options = O_CREAT;
|
71
|
+
} else {
|
72
|
+
options = (int)NUM2INT(optarg);
|
73
|
+
}
|
74
|
+
|
75
|
+
if(NIL_P(size)) {
|
76
|
+
size = (size_t)INT2FIX(0);
|
77
|
+
}
|
78
|
+
|
79
|
+
Data_Get_Struct(klass, jlog_obj, jo);
|
80
|
+
|
81
|
+
jo->ctx = jlog_new(StringValuePtr(path));
|
82
|
+
jo->path = strdup(StringValuePtr(path));
|
83
|
+
jo->auto_checkpoint = 0;
|
84
|
+
jo->start = zeroed;
|
85
|
+
jo->prev = zeroed;
|
86
|
+
jo->last = zeroed;
|
87
|
+
jo->end = zeroed;
|
88
|
+
|
89
|
+
|
90
|
+
if(!jo->ctx) {
|
91
|
+
rJlog_free(jo);
|
92
|
+
rb_raise(eJlog, "jlog_new(%s) failed", StringValuePtr(path));
|
93
|
+
}
|
94
|
+
|
95
|
+
if(options & O_CREAT) {
|
96
|
+
if(jlog_ctx_init(jo->ctx) != 0) {
|
97
|
+
if(jlog_ctx_err(jo->ctx) == JLOG_ERR_CREATE_EXISTS) {
|
98
|
+
if(options & O_EXCL) {
|
99
|
+
rJlog_free(jo);
|
100
|
+
rb_raise(eJlog, "file already exists: %s", StringValuePtr(path));
|
101
|
+
}
|
102
|
+
}else {
|
103
|
+
rJlog_raise(jo, "Error initializing jlog");
|
104
|
+
}
|
105
|
+
}
|
106
|
+
jlog_ctx_close(jo->ctx);
|
107
|
+
jo->ctx = jlog_new(StringValuePtr(path));
|
108
|
+
if(!jo->ctx) {
|
109
|
+
rJlog_free(jo);
|
110
|
+
rb_raise(eJlog, "jlog_new(%s) failed after successful init", StringValuePtr(path));
|
111
|
+
}
|
112
|
+
rJlog_populate_subscribers(klass);
|
113
|
+
}
|
114
|
+
|
115
|
+
if(!jo) {
|
116
|
+
rb_raise(eJlog, "jo wasn't initialized.");
|
117
|
+
}
|
118
|
+
|
119
|
+
return klass;
|
120
|
+
}
|
121
|
+
|
122
|
+
static VALUE rJlog_s_alloc(VALUE klass)
|
123
|
+
{
|
124
|
+
Jlog jo = ALLOC(jlog_obj);
|
125
|
+
|
126
|
+
return Data_Wrap_Struct(klass, rJlog_mark, rJlog_free, jo);
|
127
|
+
}
|
128
|
+
|
129
|
+
VALUE rJlog_add_subscriber(int argc, VALUE* argv, VALUE self)
|
130
|
+
{
|
131
|
+
VALUE s;
|
132
|
+
VALUE w;
|
133
|
+
unsigned int whence;
|
134
|
+
Jlog jo;
|
135
|
+
|
136
|
+
rb_scan_args(argc, argv, "11", &s, &w);
|
137
|
+
|
138
|
+
if(NIL_P(w)) {
|
139
|
+
whence = 0;
|
140
|
+
} else {
|
141
|
+
whence = NUM2UINT(w);
|
142
|
+
}
|
143
|
+
|
144
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
145
|
+
|
146
|
+
if(!jo || !jo->ctx || jlog_ctx_add_subscriber(jo->ctx, StringValuePtr(s), whence) != 0) {
|
147
|
+
return Qfalse;
|
148
|
+
}
|
149
|
+
|
150
|
+
rJlog_populate_subscribers(self);
|
151
|
+
|
152
|
+
return Qtrue;
|
153
|
+
}
|
154
|
+
|
155
|
+
|
156
|
+
VALUE rJlog_remove_subscriber(VALUE self, VALUE subscriber)
|
157
|
+
{
|
158
|
+
Jlog jo;
|
159
|
+
int res;
|
160
|
+
|
161
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
162
|
+
res = jlog_ctx_remove_subscriber(jo->ctx, StringValuePtr(subscriber));
|
163
|
+
if(!jo || !jo->ctx || res != 0)
|
164
|
+
{
|
165
|
+
return res;
|
166
|
+
}
|
167
|
+
|
168
|
+
rJlog_populate_subscribers(self);
|
169
|
+
|
170
|
+
return Qtrue;
|
171
|
+
}
|
172
|
+
|
173
|
+
void rJlog_populate_subscribers(VALUE self)
|
174
|
+
{
|
175
|
+
char **list;
|
176
|
+
int i;
|
177
|
+
Jlog jo;
|
178
|
+
VALUE subscribers = rb_ary_new();
|
179
|
+
|
180
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
181
|
+
|
182
|
+
if(!jo || !jo->ctx)
|
183
|
+
{
|
184
|
+
rb_raise(eJlog, "Invalid jlog context");
|
185
|
+
}
|
186
|
+
|
187
|
+
jlog_ctx_list_subscribers(jo->ctx, &list);
|
188
|
+
for(i=0; list[i]; i++ ) {
|
189
|
+
rb_ary_push(subscribers, rb_str_new2(list[i]));
|
190
|
+
}
|
191
|
+
jlog_ctx_list_subscribers_dispose(jo->ctx, list);
|
192
|
+
|
193
|
+
rb_iv_set(self, "@subscribers", subscribers);
|
194
|
+
}
|
195
|
+
|
196
|
+
VALUE rJlog_list_subscribers(VALUE self)
|
197
|
+
{
|
198
|
+
Jlog jo;
|
199
|
+
|
200
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
201
|
+
|
202
|
+
if(!jo || !jo->ctx)
|
203
|
+
{
|
204
|
+
rb_raise(eJlog, "Invalid jlog context");
|
205
|
+
}
|
206
|
+
|
207
|
+
rJlog_populate_subscribers(self);
|
208
|
+
|
209
|
+
return rb_iv_get(self, "@subscribers");
|
210
|
+
}
|
211
|
+
|
212
|
+
VALUE rJlog_raw_size(VALUE self)
|
213
|
+
{
|
214
|
+
size_t size;
|
215
|
+
Jlog jo;
|
216
|
+
|
217
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
218
|
+
|
219
|
+
if(!jo || !jo->ctx) {
|
220
|
+
rb_raise(eJlog, "Invalid jlog context");
|
221
|
+
}
|
222
|
+
size = jlog_raw_size(jo->ctx);
|
223
|
+
|
224
|
+
return INT2NUM(size);
|
225
|
+
}
|
226
|
+
|
227
|
+
VALUE rJlog_close(VALUE self)
|
228
|
+
{
|
229
|
+
Jlog jo;
|
230
|
+
|
231
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
232
|
+
|
233
|
+
if(!jo || !jo->ctx) return Qnil;
|
234
|
+
|
235
|
+
jlog_ctx_close(jo->ctx);
|
236
|
+
jo->ctx = NULL;
|
237
|
+
|
238
|
+
return Qtrue;
|
239
|
+
}
|
240
|
+
|
241
|
+
VALUE rJlog_inspect(VALUE self)
|
242
|
+
{
|
243
|
+
Jlog jo;
|
244
|
+
|
245
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
246
|
+
|
247
|
+
if(!jo || !jo->ctx) return Qnil;
|
248
|
+
|
249
|
+
// XXX Fill in inspect call data
|
250
|
+
|
251
|
+
return Qtrue;
|
252
|
+
}
|
253
|
+
|
254
|
+
VALUE rJlog_destroy(VALUE self)
|
255
|
+
{
|
256
|
+
Jlog jo;
|
257
|
+
|
258
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
259
|
+
|
260
|
+
if(!jo) return Qnil;
|
261
|
+
|
262
|
+
rJlog_free(jo);
|
263
|
+
|
264
|
+
return Qtrue;
|
265
|
+
}
|
266
|
+
|
267
|
+
VALUE rJlog_W_open(VALUE self)
|
268
|
+
{
|
269
|
+
Jlog_Writer jo;
|
270
|
+
|
271
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
272
|
+
|
273
|
+
if(!jo || !jo->ctx) {
|
274
|
+
rb_raise(eJlog, "Invalid jlog context");
|
275
|
+
}
|
276
|
+
|
277
|
+
if(jlog_ctx_open_writer(jo->ctx) != 0) {
|
278
|
+
rJlog_raise(jo, "jlog_ctx_open_writer failed");
|
279
|
+
}
|
280
|
+
|
281
|
+
return Qtrue;
|
282
|
+
}
|
283
|
+
|
284
|
+
VALUE rJlog_W_write(VALUE self, VALUE msg)
|
285
|
+
{
|
286
|
+
int err;
|
287
|
+
Jlog_Writer jo;
|
288
|
+
|
289
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
290
|
+
|
291
|
+
if(!jo || !jo->ctx) {
|
292
|
+
rb_raise(eJlog, "Invalid jlog context");
|
293
|
+
}
|
294
|
+
|
295
|
+
#if !defined(RSTRING_LEN)
|
296
|
+
# define RSTRING_LEN(x) (RSTRING(x)->len)
|
297
|
+
#endif
|
298
|
+
err = jlog_ctx_write(jo->ctx, StringValuePtr(msg), (size_t) RSTRING_LEN(msg));
|
299
|
+
if(err != 0) {
|
300
|
+
return Qfalse;
|
301
|
+
} else {
|
302
|
+
return Qtrue;
|
303
|
+
}
|
304
|
+
}
|
305
|
+
|
306
|
+
|
307
|
+
VALUE rJlog_R_open(VALUE self, VALUE subscriber)
|
308
|
+
{
|
309
|
+
Jlog_Reader jo;
|
310
|
+
int err;
|
311
|
+
|
312
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
313
|
+
|
314
|
+
if(!jo || !jo->ctx) {
|
315
|
+
rb_raise(eJlog, "Invalid jlog context");
|
316
|
+
}
|
317
|
+
|
318
|
+
err = jlog_ctx_open_reader(jo->ctx, StringValuePtr(subscriber));
|
319
|
+
|
320
|
+
if(err != 0) {
|
321
|
+
rJlog_raise(jo, "jlog_ctx_open_reader failed");
|
322
|
+
}
|
323
|
+
|
324
|
+
return Qtrue;
|
325
|
+
}
|
326
|
+
|
327
|
+
|
328
|
+
VALUE rJlog_R_read(VALUE self)
|
329
|
+
{
|
330
|
+
const jlog_id epoch = {0, 0};
|
331
|
+
jlog_id cur = {0, 0};
|
332
|
+
jlog_message message;
|
333
|
+
int cnt;
|
334
|
+
Jlog_Reader jo;
|
335
|
+
|
336
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
337
|
+
|
338
|
+
if(!jo || !jo->ctx) {
|
339
|
+
rb_raise(eJlog, "Invalid jlog context");
|
340
|
+
}
|
341
|
+
|
342
|
+
// If start is unset, read the interval
|
343
|
+
if(jo->error || !memcmp(&jo->start, &epoch, sizeof(jlog_id)))
|
344
|
+
{
|
345
|
+
jo->error = 0;
|
346
|
+
cnt = jlog_ctx_read_interval(jo->ctx, &jo->start, &jo->end);
|
347
|
+
if(cnt == 0 || (cnt == -1 && jlog_ctx_err(jo->ctx) == JLOG_ERR_FILE_OPEN)) {
|
348
|
+
jo->start = epoch;
|
349
|
+
jo->end = epoch;
|
350
|
+
return Qnil;
|
351
|
+
}
|
352
|
+
else if(cnt == -1)
|
353
|
+
rJlog_raise(jo, "jlog_ctx_read_interval_failed");
|
354
|
+
}
|
355
|
+
|
356
|
+
// If last is unset, start at the beginning
|
357
|
+
if(!memcmp(&jo->last, &epoch, sizeof(jlog_id))) {
|
358
|
+
cur = jo->start;
|
359
|
+
} else {
|
360
|
+
// if we've already read the end, return; otherwise advance
|
361
|
+
cur = jo->last;
|
362
|
+
if(!memcmp(&jo->prev, &jo->end, sizeof(jlog_id))) {
|
363
|
+
jo->start = epoch;
|
364
|
+
jo->end = epoch;
|
365
|
+
return Qnil;
|
366
|
+
}
|
367
|
+
jlog_ctx_advance_id(jo->ctx, &jo->last, &cur, &jo->end);
|
368
|
+
if(!memcmp(&jo->last, &cur, sizeof(jlog_id))) {
|
369
|
+
jo->start = epoch;
|
370
|
+
jo->end = epoch;
|
371
|
+
return Qnil;
|
372
|
+
}
|
373
|
+
}
|
374
|
+
|
375
|
+
if(jlog_ctx_read_message(jo->ctx, &cur, &message) != 0) {
|
376
|
+
if(jlog_ctx_err(jo->ctx) == JLOG_ERR_FILE_OPEN) {
|
377
|
+
jo->error = 1;
|
378
|
+
rJlog_raise(jo, "jlog_ctx_read_message failed");
|
379
|
+
return Qnil;
|
380
|
+
}
|
381
|
+
|
382
|
+
// read failed; raise error but recover if read is retried
|
383
|
+
jo->error = 1;
|
384
|
+
rJlog_raise(jo, "read failed");
|
385
|
+
}
|
386
|
+
|
387
|
+
if(jo->auto_checkpoint) {
|
388
|
+
if(jlog_ctx_read_checkpoint(jo->ctx, &cur) != 0)
|
389
|
+
rJlog_raise(jo, "checkpoint failed");
|
390
|
+
|
391
|
+
// must reread the interval after a checkpoint
|
392
|
+
jo->last = epoch;
|
393
|
+
jo->prev = epoch;
|
394
|
+
jo->start = epoch;
|
395
|
+
jo->end = epoch;
|
396
|
+
} else {
|
397
|
+
// update last
|
398
|
+
jo->prev = jo->last;
|
399
|
+
jo->last = cur;
|
400
|
+
}
|
401
|
+
|
402
|
+
return rb_str_new2(message.mess);
|
403
|
+
}
|
404
|
+
|
405
|
+
VALUE rJlog_R_read_message(VALUE self)
|
406
|
+
{
|
407
|
+
const jlog_id epoch = {0, 0};
|
408
|
+
jlog_id cur = {0, 0};
|
409
|
+
jlog_message message;
|
410
|
+
int cnt;
|
411
|
+
double ts;
|
412
|
+
Jlog_Reader jo;
|
413
|
+
VALUE message_hash;
|
414
|
+
|
415
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
416
|
+
|
417
|
+
if(!jo || !jo->ctx) {
|
418
|
+
rb_raise(eJlog, "Invalid jlog context");
|
419
|
+
}
|
420
|
+
|
421
|
+
// If start is unset, read the interval
|
422
|
+
if(jo->error || !memcmp(&jo->start, &epoch, sizeof(jlog_id)))
|
423
|
+
{
|
424
|
+
jo->error = 0;
|
425
|
+
cnt = jlog_ctx_read_interval(jo->ctx, &jo->start, &jo->end);
|
426
|
+
if(cnt == 0 || (cnt == -1 && jlog_ctx_err(jo->ctx) == JLOG_ERR_FILE_OPEN)) {
|
427
|
+
jo->start = epoch;
|
428
|
+
jo->end = epoch;
|
429
|
+
return Qnil;
|
430
|
+
}
|
431
|
+
else if(cnt == -1)
|
432
|
+
rJlog_raise(jo, "jlog_ctx_read_interval_failed");
|
433
|
+
}
|
434
|
+
|
435
|
+
// If last is unset, start at the beginning
|
436
|
+
if(!memcmp(&jo->last, &epoch, sizeof(jlog_id))) {
|
437
|
+
cur = jo->start;
|
438
|
+
} else {
|
439
|
+
// if we've already read the end, return; otherwise advance
|
440
|
+
cur = jo->last;
|
441
|
+
if(!memcmp(&jo->prev, &jo->end, sizeof(jlog_id))) {
|
442
|
+
jo->start = epoch;
|
443
|
+
jo->end = epoch;
|
444
|
+
return Qnil;
|
445
|
+
}
|
446
|
+
jlog_ctx_advance_id(jo->ctx, &jo->last, &cur, &jo->end);
|
447
|
+
if(!memcmp(&jo->last, &cur, sizeof(jlog_id))) {
|
448
|
+
jo->start = epoch;
|
449
|
+
jo->end = epoch;
|
450
|
+
return Qnil;
|
451
|
+
}
|
452
|
+
}
|
453
|
+
|
454
|
+
if(jlog_ctx_read_message(jo->ctx, &cur, &message) != 0) {
|
455
|
+
if(jlog_ctx_err(jo->ctx) == JLOG_ERR_FILE_OPEN) {
|
456
|
+
jo->error = 1;
|
457
|
+
rJlog_raise(jo, "jlog_ctx_read_message failed");
|
458
|
+
return Qnil;
|
459
|
+
}
|
460
|
+
|
461
|
+
// read failed; raise error but recover if read is retried
|
462
|
+
jo->error = 1;
|
463
|
+
rJlog_raise(jo, "read failed");
|
464
|
+
}
|
465
|
+
|
466
|
+
if(jo->auto_checkpoint) {
|
467
|
+
if(jlog_ctx_read_checkpoint(jo->ctx, &cur) != 0)
|
468
|
+
rJlog_raise(jo, "checkpoint failed");
|
469
|
+
|
470
|
+
// must reread the interval after a checkpoint
|
471
|
+
jo->last = epoch;
|
472
|
+
jo->prev = epoch;
|
473
|
+
jo->start = epoch;
|
474
|
+
jo->end = epoch;
|
475
|
+
} else {
|
476
|
+
// update last
|
477
|
+
jo->prev = jo->last;
|
478
|
+
jo->last = cur;
|
479
|
+
}
|
480
|
+
|
481
|
+
ts = message.header->tv_sec+(message.header->tv_usec/1000000.0);
|
482
|
+
|
483
|
+
message_hash = rb_hash_new();
|
484
|
+
rb_hash_aset(message_hash, message_sym, rb_str_new2(message.mess));
|
485
|
+
rb_hash_aset(message_hash, timestamp_sym, rb_float_new(ts));
|
486
|
+
|
487
|
+
|
488
|
+
return message_hash;
|
489
|
+
}
|
490
|
+
|
491
|
+
|
492
|
+
VALUE rJlog_R_rewind(VALUE self)
|
493
|
+
{
|
494
|
+
Jlog_Reader jo;
|
495
|
+
|
496
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
497
|
+
|
498
|
+
if(!jo || !jo->ctx) {
|
499
|
+
rb_raise(eJlog, "Invalid jlog context");
|
500
|
+
}
|
501
|
+
|
502
|
+
jo->last = jo->prev;
|
503
|
+
|
504
|
+
return Qtrue;
|
505
|
+
}
|
506
|
+
|
507
|
+
|
508
|
+
VALUE rJlog_R_checkpoint(VALUE self)
|
509
|
+
{
|
510
|
+
jlog_id epoch = { 0, 0 };
|
511
|
+
Jlog_Reader jo;
|
512
|
+
|
513
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
514
|
+
|
515
|
+
if(!jo || !jo->ctx) {
|
516
|
+
rb_raise(eJlog, "Invalid jlog context");
|
517
|
+
}
|
518
|
+
|
519
|
+
if(memcmp(&jo->last, &epoch, sizeof(jlog_id)))
|
520
|
+
{
|
521
|
+
jlog_ctx_read_checkpoint(jo->ctx, &jo->last);
|
522
|
+
|
523
|
+
// re-read the interval
|
524
|
+
jo->last = epoch;
|
525
|
+
jo->start = epoch;
|
526
|
+
jo->end = epoch;
|
527
|
+
}
|
528
|
+
|
529
|
+
return Qtrue;
|
530
|
+
}
|
531
|
+
|
532
|
+
|
533
|
+
VALUE rJlog_R_auto_checkpoint(int argc, VALUE *argv, VALUE self)
|
534
|
+
{
|
535
|
+
Jlog jo;
|
536
|
+
|
537
|
+
Data_Get_Struct(self, jlog_obj, jo);
|
538
|
+
|
539
|
+
if(!jo || !jo->ctx) {
|
540
|
+
rb_raise(eJlog, "Invalid jlog context");
|
541
|
+
}
|
542
|
+
|
543
|
+
if(argc > 0) {
|
544
|
+
int ac = FIX2INT(argv[0]);
|
545
|
+
jo->auto_checkpoint = ac;
|
546
|
+
}
|
547
|
+
|
548
|
+
return INT2FIX(jo->auto_checkpoint);
|
549
|
+
}
|
550
|
+
|
551
|
+
|
552
|
+
void Init_jlog(void) {
|
553
|
+
message_sym = ID2SYM(rb_intern("message"));
|
554
|
+
timestamp_sym = ID2SYM(rb_intern("timestamp"));
|
555
|
+
|
556
|
+
cJlog = rb_define_class("Jlog", rb_cObject);
|
557
|
+
cJlogWriter = rb_define_class_under(cJlog, "Writer", cJlog);
|
558
|
+
cJlogReader = rb_define_class_under(cJlog, "Reader", cJlog);
|
559
|
+
|
560
|
+
eJlog = rb_define_class_under(cJlog, "Error", rb_eStandardError);
|
561
|
+
|
562
|
+
rb_define_method(cJlog, "initialize", rJlog_initialize, -1);
|
563
|
+
rb_define_alloc_func(cJlog, rJlog_s_alloc);
|
564
|
+
|
565
|
+
rb_define_method(cJlog, "add_subscriber", rJlog_add_subscriber, -1);
|
566
|
+
rb_define_method(cJlog, "remove_subscriber", rJlog_remove_subscriber, 1);
|
567
|
+
rb_define_method(cJlog, "list_subscribers", rJlog_list_subscribers, 0);
|
568
|
+
rb_define_method(cJlog, "raw_size", rJlog_raw_size, 0);
|
569
|
+
rb_define_method(cJlog, "close", rJlog_close, 0);
|
570
|
+
rb_define_method(cJlog, "destroy", rJlog_destroy, 0);
|
571
|
+
rb_define_method(cJlog, "inspect", rJlog_inspect, 0);
|
572
|
+
|
573
|
+
rb_define_alias(cJlog, "size", "raw_size");
|
574
|
+
|
575
|
+
rb_define_method(cJlogWriter, "initialize", rJlog_initialize, -1);
|
576
|
+
rb_define_alloc_func(cJlogWriter, rJlog_s_alloc);
|
577
|
+
|
578
|
+
rb_define_method(cJlogWriter, "open", rJlog_W_open, 0);
|
579
|
+
rb_define_method(cJlogWriter, "write", rJlog_W_write, 1);
|
580
|
+
|
581
|
+
rb_define_method(cJlogReader, "initialize", rJlog_initialize, -1);
|
582
|
+
rb_define_alloc_func(cJlogReader, rJlog_s_alloc);
|
583
|
+
|
584
|
+
rb_define_method(cJlogReader, "open", rJlog_R_open, 1);
|
585
|
+
rb_define_method(cJlogReader, "read", rJlog_R_read, 0);
|
586
|
+
rb_define_method(cJlogReader, "read_message", rJlog_R_read_message, 0);
|
587
|
+
rb_define_method(cJlogReader, "rewind", rJlog_R_rewind, 0);
|
588
|
+
rb_define_method(cJlogReader, "checkpoint", rJlog_R_checkpoint, 0);
|
589
|
+
rb_define_method(cJlogReader, "auto_checkpoint", rJlog_R_auto_checkpoint, -1);
|
590
|
+
}
|
data/jlog.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'jlog/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "jlog"
|
8
|
+
spec.version = Jlog::VERSION
|
9
|
+
spec.authors = ["Ezekiel Templin", "Tyler McMullen"]
|
10
|
+
spec.email = ["ezekiel@fastly.com", "tyler@fastly.com"]
|
11
|
+
spec.description = %q{Ruby C-extension for JLog}
|
12
|
+
spec.summary = %q{A Ruby C-extenion for using JLog}
|
13
|
+
spec.homepage = "https://github.com/fastly/jlog-ruby"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.extensions = ['ext/jlog/extconf.rb']
|
18
|
+
spec.test_files = ["spec"]
|
19
|
+
spec.require_paths = ["lib", "ext"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rake-compiler"
|
24
|
+
spec.add_development_dependency "minitest", "~> 5.0.8"
|
25
|
+
end
|
data/lib/jlog.rb
ADDED
data/lib/jlog/version.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Jlog::Reader" do
|
4
|
+
let(:jlog) { Jlog.new('/tmp/junit.log') }
|
5
|
+
let(:reader) { Jlog::Reader.new('/tmp/junit.log') }
|
6
|
+
|
7
|
+
before do
|
8
|
+
jlog.add_subscriber('TestSub')
|
9
|
+
writer = Jlog::Writer.new('/tmp/junit.log')
|
10
|
+
|
11
|
+
writer.open
|
12
|
+
1.upto(10) do |n|
|
13
|
+
writer.write("Test Unit #{n}")
|
14
|
+
end
|
15
|
+
writer.close
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should be able to access a reader subscribed to a log" do
|
19
|
+
reader.open('TestSub')
|
20
|
+
reader.close
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should read a hash" do
|
24
|
+
reader.open('TestSub')
|
25
|
+
msg = reader.read_message
|
26
|
+
reader.checkpoint
|
27
|
+
|
28
|
+
assert_kind_of String, msg[:message]
|
29
|
+
assert_kind_of Float, msg[:timestamp]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should read from the proper checkpoint" do
|
33
|
+
reader.open('TestSub')
|
34
|
+
first_entry = reader.read
|
35
|
+
reader.close
|
36
|
+
|
37
|
+
reader = Jlog::Reader.new('/tmp/junit.log')
|
38
|
+
reader.open('TestSub')
|
39
|
+
assert_equal first_entry, reader.read, "Message wasn't appropriately checkpointed"
|
40
|
+
reader.close
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should be able to rewind" do
|
44
|
+
reader.open('TestSub')
|
45
|
+
res1 = reader.read
|
46
|
+
reader.rewind
|
47
|
+
res2 = reader.read
|
48
|
+
|
49
|
+
assert_equal res1, res2, "Messages do not match"
|
50
|
+
|
51
|
+
reader.checkpoint
|
52
|
+
reader.close
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should be able to checkpoint" do
|
56
|
+
reader.open('TestSub')
|
57
|
+
res1 = reader.read
|
58
|
+
reader.checkpoint
|
59
|
+
reader.rewind
|
60
|
+
res2 = reader.read
|
61
|
+
|
62
|
+
refute_equal res1, res2, "Checkpointed messages should not match"
|
63
|
+
|
64
|
+
reader.close
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Jlog::Writer' do
|
4
|
+
let(:writer) { Jlog::Writer.new('/tmp/junit.log') }
|
5
|
+
|
6
|
+
it "it should be able to open a log for writing" do
|
7
|
+
assert_kind_of Jlog::Writer, writer, "Jlog::Writer Object creation failed"
|
8
|
+
writer.open
|
9
|
+
writer.close
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be able to write to an open log" do
|
13
|
+
writer.open
|
14
|
+
|
15
|
+
1.upto(10) do |n|
|
16
|
+
writer.write("Test Unit #{n}")
|
17
|
+
end
|
18
|
+
|
19
|
+
writer.close
|
20
|
+
end
|
21
|
+
end
|
data/spec/jlog_spec.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Jlog' do
|
4
|
+
let(:jlog) { Jlog.new('/tmp/junit.log') }
|
5
|
+
|
6
|
+
it "should work" do
|
7
|
+
assert_kind_of Jlog, jlog, "JLog Object creation failed"
|
8
|
+
jlog.close
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should be able to add subscribers" do
|
12
|
+
jlog.add_subscriber("TestSub")
|
13
|
+
jlog.add_subscriber("TestSubRemove")
|
14
|
+
|
15
|
+
subscribers = jlog.list_subscribers
|
16
|
+
subscribers.delete_if do |sub|
|
17
|
+
sub =~ /^TestSub/
|
18
|
+
end
|
19
|
+
|
20
|
+
assert_equal 0, subscribers.size
|
21
|
+
jlog.close
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should be able to remove subscribers" do
|
25
|
+
jlog.remove_subscriber("TestSubRemove")
|
26
|
+
jlog.close
|
27
|
+
|
28
|
+
jlog = Jlog.new('/tmp/junit.log')
|
29
|
+
jlog.list_subscribers.each do |s|
|
30
|
+
refute_equal "TestSubRemove", s, "Test Subscriber was not removed"
|
31
|
+
end
|
32
|
+
jlog.close
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be able to see subscribers added by others" do
|
36
|
+
assert_equal 0, jlog.list_subscribers.size
|
37
|
+
new_jlog = Jlog.new('/tmp/junit.log')
|
38
|
+
new_jlog.add_subscriber('NewSubscriber')
|
39
|
+
assert_equal 1, jlog.list_subscribers.size
|
40
|
+
assert_equal 1, new_jlog.list_subscribers.size
|
41
|
+
end
|
42
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jlog
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ezekiel Templin
|
8
|
+
- Tyler McMullen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-03-25 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.3'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.3'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rake-compiler
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: minitest
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 5.0.8
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 5.0.8
|
70
|
+
description: Ruby C-extension for JLog
|
71
|
+
email:
|
72
|
+
- ezekiel@fastly.com
|
73
|
+
- tyler@fastly.com
|
74
|
+
executables: []
|
75
|
+
extensions:
|
76
|
+
- ext/jlog/extconf.rb
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- ".gitignore"
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- ext/jlog/extconf.rb
|
85
|
+
- ext/jlog/jlog.c
|
86
|
+
- jlog.gemspec
|
87
|
+
- lib/jlog.rb
|
88
|
+
- lib/jlog/version.rb
|
89
|
+
- spec/jlog/reader_spec.rb
|
90
|
+
- spec/jlog/writer_spec.rb
|
91
|
+
- spec/jlog_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
homepage: https://github.com/fastly/jlog-ruby
|
94
|
+
licenses:
|
95
|
+
- MIT
|
96
|
+
metadata: {}
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
- ext
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 2.2.2
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: A Ruby C-extenion for using JLog
|
118
|
+
test_files: []
|