├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── lib ├── console.js ├── graylog.js ├── levels.js ├── logger.js └── logmagic.js ├── package.json └── tests ├── bench.js └── t.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Log Magic 2 | Copyright (c) 2011, Paul Querna 3 | 4 | This product includes software developed by 5 | Paul Querna (http://paul.querna.org). 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Welcome to Log Magic. 2 | ==================== 3 | 4 | This project is stable. It is used in production by various companies. 5 | 6 | The goal is to have a fast and easy to use logging subsystem that can be dynamically 7 | reconfigured to provide insight into production systems. It supports being used from within a normal Node.js, [Electron Application](http://electron.atom.io/), or in a Web Browser (via [Browserify](http://browserify.org/)). 8 | 9 | Logmagic does its magic by generating objects with generated functions that are only modified 10 | when the logging system is reconfigured, thus your entire logging path is contained within 11 | long-lived functions that v8/JS engines are able to JIT. 12 | 13 | Getting Started 14 | ==================== 15 | 16 | If you had a file named like, "lib/foo/bar.js", at the top of it, you would put the following: 17 | 18 | var log = require('logmagic').local('mylib.foo.bar'); 19 | 20 | Then inside bar.js, you would just use the logger like any normal logger: 21 | 22 | log.info("Hello!") 23 | log.error("By default, format strings are not used.", {SOME_VAR: "myvalue"}) 24 | log.errorf("Just add 'f' to any log method, and you get format strings too: ${SOME_VAR}", {SOME_VAR: "myvalue"}) 25 | 26 | In any other part of your application, you can reconfigure the logging subsystem at runtime, 27 | making it easy to change log levels for specific modules dynamically. 28 | 29 | var logmagic = require('logmagic'); 30 | logmagic.registerSink("mysink", function(module, level, message) { console.log(message); }); 31 | 32 | /* Send Info an higher in the root logger to stdout */ 33 | logmagic.route("__root__", logmagic.INFO, "stdout") 34 | 35 | /* Reconfigure all children of mylib to log all debug messages to your custom sink */ 36 | logmagic.route("mylib.*", logmagic.DEBUG, "mysink") 37 | 38 | 39 | Builtin sinks include: 40 | 41 | * pretty-printed console (colors, easy to read) 42 | * Graylog2-style JSON to stderr 43 | 44 | -------------------------------------------------------------------------------- /lib/console.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | var chalk = require('chalk'); 19 | 20 | var levels = require('./levels'); 21 | 22 | var insideElectron = process.versions["electron"] ? true : false; 23 | var insideBrowser = process.browser || insideElectron; 24 | 25 | function needsQuoting(text) { 26 | var ch; 27 | for (var i = 0; i < text.length; i++) { 28 | ch = text[i]; 29 | if (ch >= 'a' && ch <= 'z') { 30 | continue; 31 | } 32 | if (ch >= 'A' && ch <= 'Z') { 33 | continue; 34 | } 35 | if (ch >= '0' && ch <= '9') { 36 | continue 37 | } 38 | if (ch == '-' || ch == '.') { 39 | continue 40 | } 41 | return true 42 | } 43 | return false 44 | } 45 | 46 | var tmp = []; 47 | 48 | function flush() { 49 | if (insideBrowser) { 50 | console.log(tmp.join("")); 51 | tmp.length = 0; 52 | } 53 | } 54 | 55 | function write(str) { 56 | if (insideBrowser) { 57 | if (str != "\n") { 58 | tmp.push(str); 59 | } 60 | } else { 61 | process.stdout.write(str); 62 | } 63 | } 64 | 65 | function out(key, value) { 66 | if (typeof value === 'string') { 67 | if (needsQuoting(value)) { 68 | write(key + "=" + JSON.stringify(value) + " "); 69 | } else { 70 | write(key + "=" + value + " "); 71 | } 72 | } else if (value instanceof Error) { 73 | write(key + "=" + JSON.stringify(value.toString()) + " "); 74 | } else { 75 | write(key + "=" + JSON.stringify(value) + " "); 76 | } 77 | } 78 | 79 | function clog(options) { 80 | var useColor = !options.disable_colors; 81 | var kvout = out; 82 | if (useColor) { 83 | return function(modulename, level, message, obj) { 84 | var m = new Date().toISOString(); 85 | var color = chalk.blue; 86 | 87 | if (level >= levels.DEBUG) { 88 | color = chalk.gray; 89 | } else if (level >= levels.WARNING) { 90 | color = chalk.yellow; 91 | } else if (level >= levels.ERR) { 92 | color = chalk.red; 93 | } 94 | 95 | write(color(levels.strings[level]) + " " + m + " " + message + " "); 96 | 97 | kvout(color('facility'), modulename); 98 | if (obj) { 99 | var keys = Object.keys(obj); 100 | if (options.disable_sort) { 101 | keys.sort(); 102 | } 103 | for (var i = 0; i < keys.length; i++) { 104 | kvout(color(keys[i]), obj[keys[i]]); 105 | } 106 | } 107 | 108 | write("\n"); 109 | flush(); 110 | }; 111 | 112 | } 113 | return function(modulename, level, message, obj) { 114 | var m = new Date().toISOString(); 115 | 116 | kvout("time", m); 117 | kvout("level", levels.strings[level]); 118 | kvout("msg", message); 119 | 120 | if (obj) { 121 | var keys = Object.keys(obj); 122 | if (options.disable_sort) { 123 | keys.sort(); 124 | } 125 | for (var i = 0; i < keys.length; i++) { 126 | kvout(keys[i], obj[keys[i]]); 127 | } 128 | } 129 | 130 | write("\n"); 131 | flush(); 132 | /* TODO: improve */ 133 | // var fm = obj['full_message'] ? "\n " + obj['full_message'] : ""; 134 | // obj['full_message'] = undefined; 135 | }; 136 | } 137 | 138 | // TODO(pquerna): expose more options; 139 | module.exports = exports = clog({ 140 | disable_sort: false 141 | }); 142 | 143 | -------------------------------------------------------------------------------- /lib/graylog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | var os = require('os'), 19 | host = process.env.FQDN || os.hostname(); 20 | 21 | var logobj = { 22 | version: "1.0", 23 | host: host, 24 | timestamp: null, 25 | short_message: null, 26 | full_message: null, 27 | timestamp: null, 28 | level: null, 29 | facility: null, 30 | }; 31 | 32 | var logcache = {}; 33 | 34 | function clone(obj) { 35 | /* Shallow object clone */ 36 | var target = {}; 37 | for (var i in obj) { 38 | if (obj.hasOwnProperty(i)) { 39 | target[i] = obj[i]; 40 | } 41 | } 42 | return target; 43 | } 44 | 45 | exports.logstderr = function(modulename, level, message, obj) { 46 | /* Outputs a GLEF-style JSON to stderr */ 47 | var str = exports.logstr(modulename, level, message, obj); 48 | process.stderr.write(str + "\n"); 49 | }; 50 | 51 | exports.logstr = function(module, level, message, obj) { 52 | 53 | if (level > 7) { 54 | level = 7; 55 | } 56 | 57 | var l = null; 58 | 59 | if (obj) { 60 | /* begin fucking voodoo */ 61 | 62 | /** 63 | * The 'easy' way to do this, is to create a new 64 | * object every time: 65 | * l = clone(logobj); 66 | * 67 | * But because of how node stores things in its slots, 68 | * this is about 50% as fast as this hack using the keys 69 | * of an object to store only one instance of it.... 70 | * 71 | * The observation we make is that most applications have a 72 | * limited set of parameters that they pass into be logged, 73 | * and the 'key' to this log object is almost always a static 74 | * string. 75 | */ 76 | var keys = ""; 77 | /* This is faster than Object.keys(obj).join(""); */ 78 | for (var i in obj) { 79 | keys += i; 80 | } 81 | 82 | l = logcache[keys]; 83 | if (!l) { 84 | l = clone(logobj); 85 | logcache[keys] = l; 86 | } 87 | 88 | for (var i in obj) { 89 | if (obj.hasOwnProperty(i)) { 90 | if (i == 'full_message') { 91 | l['full_message'] = obj[i]; 92 | } 93 | /* Avoid getting a warning from graylog: 94 | * 95 | * WARN : org.graylog2.messagehandlers.syslog.SyslogEventHandler - \ 96 | * Client tried to override _id field! Skipped field, but still storing message. 97 | */ 98 | else if (i == 'id') { 99 | l["_objid"] = obj[i]; 100 | } else { 101 | l["_" + i] = obj[i]; 102 | } 103 | } 104 | } 105 | } else { 106 | l = logobj; 107 | } 108 | 109 | 110 | l.facility = module; 111 | l.timestamp = new Date().getTime(); 112 | l.short_message = message; 113 | l.level = level; 114 | 115 | return JSON.stringify(l); 116 | } 117 | -------------------------------------------------------------------------------- /lib/levels.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | /* Based on the Log levels available in Apache HTTP Server. */ 19 | exports.EMERG = 0; /* system is unusable */ 20 | exports.ALERT = 1; /* action must be taken immediately */ 21 | exports.CRIT = 2; /* critical conditions */ 22 | exports.ERR = 3; /* error conditions */ 23 | exports.WARNING = 4; /* warning conditions */ 24 | exports.NOTICE = 5; /* normal but significant condition */ 25 | exports.INFO = 6; /* informational */ 26 | exports.DEBUG = 7; /* debug-level messages */ 27 | exports.TRACE1 = 8; /* trace-level 1 messages */ 28 | exports.TRACE2 = 9; /* trace-level 2 messages */ 29 | exports.TRACE3 = 10; /* trace-level 3 messages */ 30 | exports.TRACE4 = 11; /* trace-level 4 messages */ 31 | exports.TRACE5 = 12; /* trace-level 5 messages */ 32 | exports.TRACE6 = 13; /* trace-level 6 messages */ 33 | exports.TRACE7 = 14; /* trace-level 7 messages */ 34 | exports.TRACE8 = 15; /* trace-level 8 messages */ 35 | 36 | var log_strings = ["EMERG", 37 | "ALERT", 38 | "CRIT", 39 | "ERR", 40 | "WARNING", 41 | "NOTICE", 42 | "INFO", 43 | "DEBUG", 44 | "TRACE1", 45 | "TRACE2", 46 | "TRACE3", 47 | "TRACE4", 48 | "TRACE5", 49 | "TRACE6", 50 | "TRACE7", 51 | "TRACE8"]; 52 | 53 | exports.strings = log_strings; 54 | 55 | var log_aliases = { 56 | "WARN": "WARNING", 57 | "ERROR": "ERR", 58 | "DBG": "DEBUG", 59 | "MSG": "INFO", 60 | "SILLY": "TRACE1", 61 | "TRACE": "TRACE1" 62 | }; 63 | 64 | exports.aliases = log_aliases; 65 | 66 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | var util = require('util'); 19 | var events = require('events'); 20 | 21 | var levels = require('./levels'); 22 | 23 | function LoggerProxy(modulename) { 24 | events.EventEmitter.call(this); 25 | this.modulename = modulename; 26 | this.loglevel = -1; 27 | } 28 | util.inherits(Logger, events.EventEmitter); 29 | 30 | function Logger() { 31 | events.EventEmitter.call(this); 32 | this._sinks = {}; 33 | this._loggers = []; 34 | this._routes = []; 35 | this._rewriters = []; 36 | } 37 | util.inherits(Logger, events.EventEmitter); 38 | 39 | module.exports = exports = Logger; 40 | 41 | Logger.prototype._applyRewrites = function(modulename, level, msg, extra) { 42 | var i; 43 | for (i = 0; i < this._rewriters.length; i++) { 44 | extra = this._rewriters[i](modulename, level, msg, extra); 45 | } 46 | return extra; 47 | }; 48 | 49 | Logger.prototype._buildLogMethod = function(modulename, level, callback) { 50 | var self = this; 51 | 52 | if (level >= levels.TRACE1) { 53 | return function(msg, extra) { 54 | if (!extra) { 55 | extra = {}; 56 | } 57 | extra['full_message'] = new Error('Backtrace').stack; 58 | extra = self._applyRewrites(modulename, level, msg, extra); 59 | callback(modulename, level, msg, extra) 60 | } 61 | } else { 62 | return function(msg, extra) { 63 | if (!extra) { 64 | extra = {}; 65 | } 66 | extra = self._applyRewrites(modulename, level, msg, extra); 67 | callback(modulename, level, msg, extra) 68 | } 69 | } 70 | }; 71 | 72 | var format_string_re = new RegExp(/\$\{(.*?)\}/g); 73 | 74 | function applyFormatString(msg, extra) { 75 | function replaceFunction(str, p1) { 76 | if (extra.hasOwnProperty(p1)) { 77 | return extra[p1]; 78 | } 79 | return p1; 80 | } 81 | msg = msg.replace(format_string_re, replaceFunction); 82 | return msg; 83 | } 84 | ; 85 | 86 | 87 | Logger.prototype._buildFormattedLogMethod = function(modulename, level, callback) { 88 | var self = this; 89 | if (level >= exports.TRACE1) { 90 | return function(msg, extra) { 91 | if (!extra) { 92 | extra = {}; 93 | } 94 | extra['full_message'] = new Error('Backtrace').stack; 95 | extra = self._applyRewrites(modulename, level, msg, extra); 96 | msg = applyFormatString(msg, extra); 97 | callback(modulename, level, msg, extra) 98 | } 99 | } else { 100 | return function(msg, extra) { 101 | if (!extra) { 102 | extra = {}; 103 | } 104 | extra = self._applyRewrites(modulename, level, msg, extra); 105 | msg = applyFormatString(msg, extra); 106 | callback(modulename, level, msg, extra) 107 | } 108 | } 109 | }; 110 | 111 | function nullLogger() { 112 | /* Intentionally blank. */ 113 | } 114 | 115 | Logger.prototype._applyRoute = function(route, logger, modulename) { 116 | logger.loglevel = route.loglevel; 117 | for (var i = 0; i < levels.strings.length; i++) { 118 | var level = levels.strings[i]; 119 | var lognum = levels[level]; 120 | var llstr = level.toLowerCase(); 121 | 122 | if (lognum <= route.loglevel) { 123 | logger[llstr] = this._buildLogMethod(modulename, lognum, route.callback); 124 | logger[llstr + 'f'] = this._buildFormattedLogMethod(modulename, lognum, route.callback); 125 | } else { 126 | logger[llstr] = nullLogger; 127 | logger[llstr + 'f'] = nullLogger; 128 | } 129 | } 130 | 131 | for (var key in levels.aliases) { 132 | if (levels.aliases.hasOwnProperty(key)) { 133 | logger[key.toLowerCase()] = logger[levels.aliases[key].toLowerCase()]; 134 | logger[key.toLowerCase() + 'f'] = logger[levels.aliases[key].toLowerCase() + 'f']; 135 | } 136 | } 137 | }; 138 | 139 | // TODO: rewrite with a real route matcher 140 | function routeMatch(a, b) { 141 | var as = a.split('.'); 142 | var bs = b.split('.'); 143 | var i = 0; 144 | 145 | while (true) { 146 | if (as.length < i || bs.length < i) { 147 | break; 148 | } 149 | if (as[i] == bs[i]) { 150 | if (as.length == i) { 151 | return true; 152 | } 153 | i++; 154 | continue; 155 | } 156 | 157 | if (as[i] == "*") { 158 | return true; 159 | } 160 | 161 | break; 162 | } 163 | return false; 164 | } 165 | 166 | 167 | Logger.prototype._applyRoutes = function(logger) { 168 | for (var i = 0; i < this._routes.length; i++) { 169 | var r = this._routes[i]; 170 | if (r.route == "__root__") { 171 | this._applyRoute(r, logger, logger.modulename); 172 | } else if (routeMatch(r.route, logger.modulename)) { 173 | this._applyRoute(r, logger, logger.modulename); 174 | } 175 | } 176 | }; 177 | 178 | 179 | Logger.prototype.local = function(modulename) { 180 | var logProxy = new LoggerProxy(modulename); 181 | this._applyRoutes(logProxy); 182 | this._loggers.push(logProxy); 183 | return logProxy; 184 | }; 185 | 186 | 187 | Logger.prototype.registerSink = function(sinkname, callback) { 188 | this._sinks[sinkname] = callback; 189 | }; 190 | 191 | 192 | Logger.prototype.route = function(match, loglevel, sinkname) { 193 | if (!(loglevel >= levels.EMERG && loglevel <= levels.TRACE8)) { 194 | throw new Error("Invalid Log level: " + loglevel); 195 | } 196 | 197 | /* TODO: Maybe it is okay to route before we have a sink loaded (?) */ 198 | if (this._sinks[sinkname] === undefined) { 199 | throw new Error("Invalid Sink: " + sinkname); 200 | } 201 | 202 | this._routes.push({ 203 | route: match, 204 | loglevel: loglevel, 205 | callback: this._sinks[sinkname] 206 | }); 207 | 208 | for (var i = 0; i < this._loggers.length; i++) { 209 | var logger = this._loggers[i]; 210 | this._applyRoutes(logger); 211 | } 212 | }; 213 | 214 | Logger.prototype.addRewriter = function(func) { 215 | this._rewriters.push(func); 216 | }; 217 | 218 | -------------------------------------------------------------------------------- /lib/logmagic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Paul Querna 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | var graylog = require('./graylog'); 19 | var console = require('./console'); 20 | var Logger = require('./logger'); 21 | var levels = require('./levels'); 22 | 23 | var defaultLogger = null; 24 | 25 | exports.local = function(modulename) { 26 | return defaultLogger.local(modulename); 27 | }; 28 | 29 | exports.registerSink = function(sinkname, callback) { 30 | defaultLogger.registerSink(sinkname, callback); 31 | }; 32 | 33 | exports.route = function(match, loglevel, sinkname) { 34 | defaultLogger.route(match, loglevel, sinkname); 35 | }; 36 | 37 | exports.addRewriter = function(func) { 38 | defaultLogger.addRewriter(func); 39 | }; 40 | 41 | (function() { 42 | // re-export log levels. 43 | levels.strings.forEach(function(l) { 44 | exports[l] = levels[l]; 45 | }); 46 | 47 | defaultLogger = new Logger(); 48 | /* Default Sinks */ 49 | 50 | /* This is just here for initial dev work, REMOVE ME */ 51 | exports.registerSink("console", console); 52 | exports.registerSink("graylog2-stderr", graylog.logstderr); 53 | 54 | /* Default loggers */ 55 | exports.route("__root__", exports.INFO, "console"); 56 | })(); 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logmagic", 3 | "version": "0.4.0", 4 | "description": "Dynamic and Configurable logging library for node.js", 5 | "main": "lib/logmagic.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "dependencies": { 10 | "chalk": "^1.0.0" 11 | }, 12 | "devDependencies": { 13 | "jsfmt": "^0.4.1" 14 | }, 15 | "scripts": { 16 | "fix": "node_modules/.bin/jsfmt --write lib/*.js", 17 | "test": "echo \"Error: no test specified\" && exit 1" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/pquerna/node-logmagic.git" 22 | }, 23 | "keywords": [ 24 | "logging" 25 | ], 26 | "author": "Paul Querna (http://paul.querna.org/)", 27 | "license": "Apache 2.0", 28 | "bugs": { 29 | "url": "https://github.com/pquerna/node-logmagic/issues" 30 | }, 31 | "homepage": "https://github.com/pquerna/node-logmagic" 32 | } 33 | -------------------------------------------------------------------------------- /tests/bench.js: -------------------------------------------------------------------------------- 1 | var graylog = require('../lib/graylog'); 2 | var ops = 100000; 3 | 4 | var start = (new Date().getTime()); 5 | for (var i = 0; i < ops; i++) { 6 | var str = graylog.logstr("testing.example.foo", 3, "hello world", {counter: i, account_id: 42, txnid: "fxxxxx"}); 7 | var str = graylog.logstr("testing.example.bar", 9, "hello baksdfnsdf", {special: 'aaa', account_id: 42, txnid: "fxxxxx"}); 8 | } 9 | var end = (new Date().getTime()); 10 | 11 | var ms = (end - start); 12 | 13 | console.log(ops + " logstr operations in " + ms + "ms, " + (ops/ms) * 1000 + " (logstr/second)"); -------------------------------------------------------------------------------- /tests/t.js: -------------------------------------------------------------------------------- 1 | var logmagic = require('../lib/logmagic'); 2 | var log = logmagic.local('mylib.foo.bar'); 3 | //console.log(log); 4 | log.info("Hello!"); 5 | log.error("more stuff", {SOME_VAR: "myvalue"}); 6 | log.errorf("more stuff: ${SOME_VAR}", {SOME_VAR: "myvalue"}); 7 | log.trace("testing trace v0"); 8 | 9 | logmagic.route("__root__", logmagic.TRACE1, "console"); 10 | logmagic.route("__root__", logmagic.TRACE1, "graylog2-stderr"); 11 | log.trace("testing trace v1", {slug: 1}); 12 | 13 | log = logmagic.local('mylib.foo.cars'); 14 | log.trace("hello world", {counter: 33, account_id: 42, txnid: "fxxxxx", id: "i'm an id, screwing stuff up"}); 15 | logmagic.addRewriter(function(modulename, level, msg, extra) { 16 | if (extra.request) { 17 | extra.accountId = extra.request.account.id; 18 | extra.txnId = extra.request.txtId; 19 | delete extra.request; 20 | } 21 | return extra; 22 | }); 23 | 24 | log.trace("hello baksdfnsdf", {special: 'aaa', account_id: 42, txnid: "fxxxxx", full_message: "loooong message"}); 25 | 26 | log.dbg("hello xxxx", {request: {account: {id: 45}, txtId: "XXXXXXXXXXXXX"}}); 27 | 28 | //console.log(log); 29 | --------------------------------------------------------------------------------