├── .gitignore ├── LICENSE ├── README.md ├── binding.gyp ├── lib ├── journald.js ├── log_journald.js └── winston_journald.js ├── package.json ├── src └── journald_cpp.cc └── test.js /.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 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Mark Theunissen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-systemd 2 | ------------ 3 | 4 | Node.js module for native access to the journald facilities in recent 5 | versions of systemd. In particular, this capability includes passing 6 | key/value pairs as fields that journald can use for filtering. 7 | 8 | This should normally be installed using [npm][1] 9 | 10 | Also includes a plugin for [winston][0] 11 | 12 | Usage 13 | ===== 14 | 15 | Very basic (see test.js for more detailed example): 16 | 17 | ``` js 18 | var journald = require('journald').Log; 19 | journald.log('MESSAGE=hello world', 'ARG1=first_argument', 'ARG2=second_argument'); 20 | ``` 21 | 22 | Developing 23 | ========== 24 | 25 | Install node-gyp to build the extension: 26 | 27 | sudo npm install -g node-gyp 28 | 29 | Use npm to build the extension: 30 | 31 | npm install 32 | 33 | Or, build the C++ extension manually: 34 | 35 | node-gyp configure && node-gyp build 36 | 37 | Run test app: 38 | 39 | node test.js 40 | 41 | Viewing Output 42 | ============== 43 | 44 | Quick way to view output with all fields as it comes in: 45 | 46 | sudo journalctl -f -p7 --output=json-pretty 47 | 48 | [0]: https://github.com/flatiron/winston 49 | [1]: https://www.npmjs.org/package/journald 50 | 51 | LICENSE 52 | ------- 53 | 54 | (c) 2012 Mark Theunissen 55 | 56 | MIT (EXPAT) 57 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "journald_cpp", 5 | "sources": [ "src/journald_cpp.cc" ], 6 | 'libraries': [ " 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 | -------------------------------------------------------------------------------- /lib/winston_journald.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Winston Transport for outputting to the systemd journal. 3 | * 4 | * See http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html 5 | * 6 | * (C) 2012 Mark Theunissen 7 | * MIT (EXPAT) LICENCE 8 | * 9 | */ 10 | 11 | var util = require('util'), 12 | winston = require('winston'), 13 | Transport = require('winston').Transport, 14 | journald = require('./log_journald'); 15 | 16 | /** 17 | * The module's exports. 18 | */ 19 | var Journald = exports.Journald = function (options) { 20 | Transport.call(this, options); 21 | options = options || {}; 22 | 23 | this.name = 'journald'; 24 | this.priority_map = options.priority_map || {}; 25 | this.default_meta = options.default_meta || {}; 26 | }; 27 | 28 | /** 29 | * Inherit from `winston.Transport`. 30 | */ 31 | util.inherits(Journald, Transport); 32 | 33 | /** 34 | * Expose the name of this Transport on the prototype 35 | */ 36 | Journald.prototype.name = 'journald'; 37 | 38 | /** 39 | * Write to the log. The level is added to the log message, along with 40 | * the numerical priority, if there is a valid level-to-priority map entry. 41 | * 42 | * Any additional fields can be passed in the 'event_meta' parameter, which 43 | * are added to the message, overriding any fields defined as default_meta. 44 | */ 45 | Journald.prototype.log = function (level, msg, event_meta, callback) { 46 | var meta = {}; 47 | // Set default meta data. 48 | for (var key in this.default_meta || {}) { 49 | meta[key] = this.default_meta[key]; 50 | } 51 | // Merge in event_meta key/value pairs (if any) 52 | for (var key in event_meta || {}) { 53 | meta[key] = event_meta[key]; 54 | } 55 | 56 | // Set level 57 | meta.LEVEL = level; 58 | 59 | // Map level to named PRIORITY field 60 | if (this.priority_map[level] >= 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Log to the systemd journal", 3 | "version": "0.0.5", 4 | "name": "journald", 5 | "author": "Mark Theunissen", 6 | "homepage": "https://github.com/systemd/node-systemd", 7 | "directories": ["./lib", "./src"], 8 | "main": "./lib/journald", 9 | 10 | "repository": { 11 | "type" : "git", 12 | "url" : "https://github.com/systemd/node-systemd.git" 13 | }, 14 | 15 | "dependencies": { 16 | "winston": "*" 17 | }, 18 | 19 | "scripts": { 20 | "preinstall": "node-gyp clean && node-gyp configure", 21 | "install": "node-gyp build" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------