├── .gitignore ├── binding.gyp ├── lib ├── journald.js ├── winston_journald.js └── log_journald.js ├── package.json ├── LICENSE ├── README.md ├── test.js └── src └── journald_cpp.cc /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | 17 | build -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "journald_cpp", 5 | "sources": [ "src/journald_cpp.cc" ], 6 | 'libraries': [ "= 0) { 61 | meta.PRIORITY = this.priority_map[level] + ''; 62 | } 63 | 64 | // Split or use message 65 | msg_split = (msg || '').split('=', 2); 66 | if (msg_split.length > 1) { 67 | meta[msg_split[0]] = msg_split[1]; 68 | } 69 | else { 70 | meta.MESSAGE = msg_split[0]; 71 | } 72 | 73 | journald.log(meta, callback); 74 | }; 75 | -------------------------------------------------------------------------------- /lib/log_journald.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Logging to the systemd journal. 3 | * 4 | * (C) 2012 Mark Theunissen 5 | * MIT (EXPAT) LICENCE 6 | * 7 | */ 8 | 9 | var journald_cpp = require('../build/Release/journald_cpp'); 10 | 11 | var noop = function() {}; 12 | 13 | /** 14 | * Exported functionality of the log module. 15 | */ 16 | module.exports = { 17 | log: function() { 18 | if (arguments.length < 1) { 19 | throw { 20 | name: 'ArgumentsError', 21 | message: 'No arguments given' 22 | } 23 | } 24 | var args = []; 25 | for (i = 0; i < arguments.length; i++) { 26 | args.push(arguments[i]); 27 | } 28 | // If no callback is provided, add an empty one. 29 | if (typeof arguments[arguments.length-1] !== 'function') { 30 | args.push(noop); 31 | } 32 | journald.send(args); 33 | } 34 | } 35 | 36 | var journald = { 37 | send: function(args) { 38 | // Send log messages by passing an object as the first argument, 39 | // in the form: 40 | // 41 | // journald.send({ 42 | // MSG: 'Hello', 43 | // MSG2: 'World' 44 | // }); 45 | // 46 | if (typeof args[0] == 'object') { 47 | var strings = []; 48 | for (key in args[0]) { 49 | if (args[0].hasOwnProperty(key)) { 50 | var val = args[0][key]; 51 | if (typeof val != 'string') { 52 | val = JSON.stringify(val); 53 | } 54 | strings.push(key.toUpperCase() + '=' + val); 55 | } 56 | } 57 | if (strings.length > 1) { 58 | if (typeof args[1] == 'function') { 59 | strings.push(args[1]); 60 | } 61 | journald_cpp.send.apply(this, strings); 62 | } 63 | return; 64 | } 65 | 66 | // Arguments given as individual strings: 67 | // 68 | // journald.send('MSG=Hello', 'MSG2=World') 69 | // 70 | for (i = 0; i < args.length; i++) { 71 | if (typeof args[i] != 'string' && typeof args[i] != 'function') { 72 | throw { 73 | name: 'ArgumentsError', 74 | message: 'Non-string arguments given' 75 | } 76 | } 77 | } 78 | journald_cpp.send.apply(this, args); 79 | return; 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Example code for the systemd journal node extension. 3 | * 4 | * (C) 2012 Mark Theunissen 5 | * MIT (EXPAT) LICENCE 6 | * 7 | */ 8 | 9 | var winston = require('winston'), 10 | journald = require('./lib/journald').Log, 11 | journald_transport = require('./lib/journald').WinstonTransport; 12 | 13 | // Log using Winston, first add the journald transport and then send. 14 | // This will also print to the console as that is the default winston 15 | // log transport. 16 | winston.add(journald_transport.Journald); 17 | winston.info('Simple message format'); 18 | winston.info('CUSTOMKEY=Specified a key'); 19 | 20 | winston.log('error', 'MESSAGE=Multiple messages can be sent', { 21 | STATE: 'System crashing', 22 | BECAUSE: 'Someone unplugged it' 23 | }); 24 | 25 | 26 | // Or, create a winston logger instance, passing in options, i.e. 27 | // default SYSLOG_IDENTIFIER field, or level-to-priority map. 28 | var transport_instance = new (journald_transport.Journald)({ 29 | default_meta: {"SYSLOG_IDENTIFIER": "test"}, 30 | priority_map: winston.config.syslog.levels, 31 | level: "emerg", 32 | }); 33 | var log = new (winston.Logger)({ 34 | transports: [ transport_instance ] 35 | }); 36 | log.setLevels(winston.config.syslog.levels) 37 | 38 | log.debug("debug message", {ANOTHER_KEY: "ANOTHER_VALUE"}); 39 | log.info("info message"); 40 | log.notice("notice message"); 41 | log.warning("warning message"); 42 | log.error("error message"); 43 | log.crit("crit message"); 44 | log.alert("alert message"); 45 | log.emerg("emerg message"); 46 | 47 | // Log non-string data. Will call JSON.stringify() on the non-string 48 | // properties. The following results in: 49 | // "DATA" : "1" 50 | // "VARS" : "{\"a\":33,\"b\":[3,4,5,6]}" 51 | // "MYFUNC" : "undefined" 52 | log.error("Non string data", { 53 | 'data': 1, 54 | vars: { 55 | a: 33, 56 | b: [3,4,5,6] 57 | }, 58 | myfunc: function() { 59 | return; 60 | } 61 | }); 62 | 63 | // Now log directly using the journald log, you can pass as many string 64 | // parameters as you like. 65 | journald.log('MESSAGE=strings sent individually', 'ARG1=as arguments'); 66 | 67 | // Or pass as an object and optionally end with a callback function. This 68 | // one outputs the result of the call to the console, this is taken directly 69 | // from the C call to systemd, so will be 0 (pass) or 1 (fail). 70 | var callback = function(err, result) { 71 | console.warn(result); 72 | } 73 | journald.log({ 74 | MESSAGE: 'hello world', 75 | ARG1: 'first argument here', 76 | ARG2: 'second argument here', 77 | ARG3: 'another one' 78 | }, 79 | callback 80 | ); 81 | -------------------------------------------------------------------------------- /src/journald_cpp.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * Native extension that logs to the systemd journal asyncronously. It also 3 | * supports a sync mode but that isn't yet implemented in the journald.js 4 | * lib. 5 | * 6 | * (C) 2012 Mark Theunissen 7 | * MIT (EXPAT) LICENCE 8 | * 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | using namespace v8; 18 | 19 | Handle Async(const Arguments& args); 20 | void AsyncWork(uv_work_t* req); 21 | void AsyncAfter(uv_work_t* req); 22 | 23 | struct Baton { 24 | uv_work_t request; 25 | Persistent callback; 26 | bool error; 27 | int32_t result; 28 | struct iovec *iov; 29 | int argc; 30 | }; 31 | 32 | /** 33 | * Log string messages to the journal. Type checking of the arguments is performed 34 | * in the journald.js module. If the last argument is a function, we log 35 | * asyncronously and treat the function as a callback. 36 | */ 37 | Handle SdJournalSend(const Arguments& args) { 38 | HandleScope scope; 39 | int argc = args.Length(); 40 | struct iovec *iov = NULL; 41 | bool isAsync = false; 42 | 43 | // If the last argument is a function, we need to exclude it from the 44 | // string processing. 45 | if (args[argc-1]->IsFunction()) { 46 | argc--; 47 | isAsync = true; 48 | } 49 | 50 | // Regardless of whether this is sync or async, we need to create the 51 | // iovector because we won't have access to the v8 API in the worker 52 | // thread. 53 | iov = (iovec*) malloc(argc * sizeof(struct iovec)); 54 | if (!iov) { 55 | return ThrowException(String::New("Out of memory")); 56 | } 57 | for (int i = 0; i < argc; ++i) { 58 | Local v8str = args[i]->ToString(); 59 | iov[i].iov_len = v8str->Length(); 60 | iov[i].iov_base = (char*) malloc(v8str->Length() + 1); 61 | v8str->WriteAscii((char*)iov[i].iov_base, 0, iov[i].iov_len); 62 | } 63 | 64 | // Divergent paths for sync and async. 65 | if (isAsync) { 66 | Local callback = Local::Cast(args[argc]); 67 | 68 | // This creates our work request, including the libuv struct. 69 | Baton* baton = new Baton(); 70 | baton->error = false; 71 | baton->request.data = baton; 72 | baton->callback = Persistent::New(callback); 73 | baton->iov = iov; 74 | baton->argc = argc; 75 | 76 | int status = uv_queue_work(uv_default_loop(), &baton->request, AsyncWork, (uv_after_work_cb) AsyncAfter); 77 | assert(status == 0); 78 | } 79 | else { 80 | sd_journal_sendv(iov, argc); 81 | for (int i = 0; i < argc; ++i) { 82 | free(iov[i].iov_base); 83 | } 84 | free(iov); 85 | } 86 | 87 | return scope.Close(Undefined()); 88 | } 89 | 90 | /** 91 | * Perform the async work in the worker thread. No v8 API here. 92 | */ 93 | void AsyncWork(uv_work_t* req) { 94 | Baton* baton = static_cast(req->data); 95 | 96 | baton->result = sd_journal_sendv(baton->iov, baton->argc); 97 | 98 | for (int i = 0; i < baton->argc; ++i) { 99 | free(baton->iov[i].iov_base); 100 | } 101 | free(baton->iov); 102 | } 103 | 104 | /** 105 | * Once the async work completes. 106 | */ 107 | void AsyncAfter(uv_work_t* req) { 108 | HandleScope scope; 109 | Baton* baton = static_cast(req->data); 110 | 111 | // TODO: We don't yet set this error flag but the example code is 112 | // still here for future. 113 | if (baton->error) { 114 | Local err = Exception::Error(String::New("ERROR")); 115 | 116 | // Prepare the parameters for the callback function. 117 | const unsigned argc = 1; 118 | Local argv[argc] = { err }; 119 | 120 | // Wrap the callback function call in a TryCatch so that we can call 121 | // node's FatalException afterwards. This makes it possible to catch 122 | // the exception from JavaScript land using the 123 | // process.on('uncaughtException') event. 124 | TryCatch try_catch; 125 | baton->callback->Call(Context::GetCurrent()->Global(), argc, argv); 126 | if (try_catch.HasCaught()) { 127 | node::FatalException(try_catch); 128 | } 129 | } 130 | else { 131 | // If the operation succeeded, convention is to pass null as the 132 | // first argument before the result arguments, as the err parameter. 133 | const unsigned argc = 2; 134 | Local argv[argc] = { 135 | Local::New(Null()), 136 | Local::New(Integer::New(baton->result)) 137 | }; 138 | 139 | // Wrap the callback function call in a TryCatch so that we can call 140 | // node's FatalException afterwards. This makes it possible to catch 141 | // the exception from JavaScript land using the 142 | // process.on('uncaughtException') event. 143 | TryCatch try_catch; 144 | baton->callback->Call(Context::GetCurrent()->Global(), argc, argv); 145 | if (try_catch.HasCaught()) { 146 | node::FatalException(try_catch); 147 | } 148 | } 149 | 150 | // The callback is a permanent handle, so we have to dispose of it manually. 151 | baton->callback.Dispose(); 152 | delete baton; 153 | } 154 | 155 | void init(Handle target) { 156 | target->Set(String::NewSymbol("send"), FunctionTemplate::New(SdJournalSend)->GetFunction()); 157 | } 158 | 159 | NODE_MODULE(journald_cpp, init) 160 | --------------------------------------------------------------------------------