churchianity
3 years ago
commit
2fb1cab0e9
4 changed files with 354 additions and 0 deletions
-
47README.md
-
39log4jesus.default.json
-
229log4jesus.js
-
39log4jesus.json
@ -0,0 +1,47 @@ |
|||
|
|||
# Usage |
|||
|
|||
```js |
|||
const { log, logt } = require("log4jesus"); |
|||
``` |
|||
|
|||
## log |
|||
send log data to targets tagged with 'all' |
|||
```js |
|||
log("use", ["exactly", "like"], `console.log ${420 * 69}`) |
|||
``` |
|||
|
|||
## logt |
|||
send log data to only targets tagged with one of the tags in the array passed as a first arguement. |
|||
first argument can be a string or array of strings. |
|||
```js |
|||
// send only to targets with the 'verbose' tag |
|||
logt("verbose", "Download progress:", downloadProgress / totalDownloads); |
|||
|
|||
// send to targets with the 'info' and 'error' tags |
|||
log(["error", "info"], "failed to parse json at: ", filePath) |
|||
``` |
|||
|
|||
# Configuration |
|||
log4jesus supports 3 types of targets for your log data: |
|||
- file |
|||
- console |
|||
- http |
|||
|
|||
to determine what logs go to which target, edit the file `log4jesus.json`. |
|||
there are three arrays defined in that file, associated with a key of either 'file', 'console' or 'http. |
|||
in each case, the array should be an array of objects with a `tags` property (which should be a string or array of strings), as well as some other details about how this target should operate. |
|||
|
|||
## 'file' details |
|||
- path: optional string to the file to create |
|||
|
|||
## 'console' details |
|||
- stdstream: required value of 'stdout', 'stderr', or 'stdin'. It's not usually desired to make it 'stdin'. |
|||
|
|||
## 'http' details |
|||
our http object is an extension of the [nodejs http/https options object](https://nodejs.org/api/http.html#httprequesturl-options-callback). The object is passed faithfully to http/s.request, so anything you add that is valid for http/s.request will be valid here. |
|||
|
|||
Add a 'url' field to the object to specify the url for the request, which should include both the port and protocol - you usually won't need to set 'hostname' or 'host', or 'path' or 'port' or 'protocol'. |
|||
|
|||
You may commonly have to set the 'headers' object, and the 'content-type' header inside of that object, depending on your server. If it's not provided it's set to 'text/plain'. |
|||
|
@ -0,0 +1,39 @@ |
|||
{ |
|||
"file": [ |
|||
{ |
|||
"tags": "all" |
|||
} |
|||
], |
|||
"console": [ |
|||
{ |
|||
"tags": "error", |
|||
"stdstream": "stderr" |
|||
}, |
|||
{ |
|||
"tags": "warn", |
|||
"stdstream": "stdout" |
|||
}, |
|||
{ |
|||
"tags": "info", |
|||
"stdstream": "stdout" |
|||
}, |
|||
{ |
|||
"tags": "verbose", |
|||
"stdstream": "stdout" |
|||
}, |
|||
{ |
|||
"tags": [ "all", "log" ], |
|||
"stdstream": "stdout" |
|||
} |
|||
], |
|||
"http": [ |
|||
{ |
|||
"tags": "none", |
|||
"url": "https://localhost:8181", |
|||
"method": "POST", |
|||
"headers": { |
|||
"content-type": "application/json" |
|||
} |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,229 @@ |
|||
|
|||
const fs = require("fs"); |
|||
const util = require("util"); |
|||
const http = require("http"); |
|||
const https = require("https"); |
|||
|
|||
|
|||
function timestamp() { |
|||
return new Date().toISOString(); |
|||
} |
|||
|
|||
function formatOutput(...args) { |
|||
return `${timestamp()} - ${util.format(...args)}\n`; |
|||
} |
|||
|
|||
function parseStreams(config) { |
|||
if (config.file) { |
|||
for (let i = 0; i < config.file.length; i++) { |
|||
const file = config.file[i]; |
|||
let path = file.path || `${__dirname}/${timestamp()}-log.txt`; |
|||
|
|||
try { |
|||
file.stream = fs.createWriteStream(path, { flags: "a", autoClose: true }); |
|||
|
|||
} catch (e) { |
|||
console.error(`failed to open file at: ${path} for writing. Check the path in the config, and permissions for that directory and this process`); |
|||
process.exit(1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (config.console) { |
|||
for (let i = 0; i < config.console.length; i++) { |
|||
const tty = config.console[i]; |
|||
|
|||
if ( |
|||
!(tty.stdstream === "stdout" |
|||
|| tty.stdstream === "stderr" |
|||
|| tty.stdstream === "stdin") |
|||
) { |
|||
console.error(tty, "console items in config must have a key 'stdstream' set to one of the following: 'stdout', 'stderr', or 'stdin'. Check your config.") |
|||
process.exit(1); |
|||
} |
|||
|
|||
tty.stream = process[tty.stdstream]; |
|||
} |
|||
} |
|||
|
|||
if (config.http) { |
|||
for (let i = 0; i < config.http.length; i++) { |
|||
const h = config.http[i]; |
|||
|
|||
if (!h.method) { |
|||
h.method = "POST"; |
|||
|
|||
} else if (h.method === "GET") { |
|||
console.warn(`using the http method 'GET' to send your logs is not recommended - in particular if you are intending to send a request body. Some system libraries are known to strip the payload completely off of GET requests, making this unreliable, despite the http 1.1 standard technically allowing it. See: https://github.com/whatwg/fetch/issues/551#issue-235203784`); |
|||
} |
|||
|
|||
if (!h.url) { |
|||
console.error(`Please specify a 'url' on your http object in your configuration. The url should be full - the protocol and port should be included`); |
|||
process.exit(1); |
|||
} |
|||
|
|||
if (!h.headers || !h.headers["content-type"]) { |
|||
h.headers = { |
|||
"content-type": "text/plain" |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
function loadAndParseConfig(configFilePath) { |
|||
let config; |
|||
try { |
|||
const fileContents = fs.readFileSync(configFilePath, { encoding: "utf-8", flag: "r" }); |
|||
try { |
|||
config = JSON.parse(fileContents); |
|||
|
|||
} catch (e) { |
|||
console.error(`invalid JSON in your configuration file: ${configFilePath}`); |
|||
process.exit(1); |
|||
} |
|||
} catch(e) { |
|||
// no config file, warn about it, make a default
|
|||
console.warn(`no configuration file found at: ${configFilePath}. Using a default config.`); |
|||
config = { |
|||
"file": [ |
|||
{ |
|||
"tags": "all" |
|||
}, |
|||
], |
|||
"console": [ |
|||
{ |
|||
"tags": "error", |
|||
"stdstream": "stderr" |
|||
}, |
|||
{ |
|||
"tags": "warn", |
|||
"stdstream": "stdout" |
|||
}, |
|||
{ |
|||
"tags": "info", |
|||
"stdstream": "stdout" |
|||
}, |
|||
{ |
|||
"tags": "verbose", |
|||
"stdstream": "stdout" |
|||
}, |
|||
{ |
|||
"tags": [ "all", "log" ], |
|||
"stdstream": "stdout" |
|||
} |
|||
], |
|||
"http": [ |
|||
{ |
|||
"tags": "none", |
|||
"url": "http://localhost:8080" |
|||
} |
|||
] |
|||
}; |
|||
} |
|||
|
|||
parseStreams(config); |
|||
|
|||
return config; |
|||
} |
|||
|
|||
const configFilePath = __dirname + "/log4jesus.json"; |
|||
const config = loadAndParseConfig(configFilePath); |
|||
|
|||
function getStreamsOfTypeAndTags(tags, type) { |
|||
const streams = config[type]; |
|||
const out = []; |
|||
for (let i = 0; i < streams.length; i++) { |
|||
const stream = streams[i]; |
|||
|
|||
if (!Array.isArray(stream.tags)) { |
|||
stream.tags = [ stream.tags ]; |
|||
} |
|||
|
|||
for (let j = 0; j < stream.tags.length; j++) { |
|||
for (let k = 0; k < tags.length; k++) { |
|||
if (stream.tags[j] === tags[k]) { |
|||
out.push({ type, ...stream }); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return out; |
|||
} |
|||
|
|||
function getStreamsByTags(tags) { |
|||
if (!Array.isArray(tags)) { |
|||
tags = [ tags ]; |
|||
} |
|||
|
|||
return [].concat( |
|||
getStreamsOfTypeAndTags(tags, "file"), |
|||
getStreamsOfTypeAndTags(tags, "console"), |
|||
getStreamsOfTypeAndTags(tags, "http") |
|||
); |
|||
} |
|||
|
|||
// convienent list of streams that we always want to send our logs to, regardless of tags
|
|||
const alwaysStreams = getStreamsByTags("all"); |
|||
|
|||
function httpRequest(options, payload) { |
|||
const url = new URL(options.url); |
|||
const client = url.protocol === "https" ? https : http; |
|||
options.headers["content-length"] = payload.length; |
|||
const request = client.request(url, options, response => { |
|||
response.on("data", data => { |
|||
// @TODO, if you care what the server responds with
|
|||
}); |
|||
}); |
|||
|
|||
request.on("error", error => { |
|||
// @TODO, if you care about errors on the request
|
|||
}); |
|||
|
|||
request.write(payload); |
|||
request.end(); |
|||
} |
|||
|
|||
// log by itself will only log to streams tagged 'all'. if you want to specifiy tags, call 'logt' instead
|
|||
function log(...args) { |
|||
const output = formatOutput(...args); |
|||
|
|||
for (let i = 0; i < alwaysStreams.length; i++) { |
|||
const s = alwaysStreams[i]; |
|||
|
|||
if (s.stream) { |
|||
s.stream.write(output); |
|||
|
|||
} else if (s.type === "http") { |
|||
httpRequest(s, output); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// |tags| should be a string or array of strings identifying a key in the above configuration object
|
|||
// which informs the logger 'where' to output the log
|
|||
function logt(tags, ...args) { |
|||
if (!args) { |
|||
log(tags); |
|||
return; |
|||
} |
|||
|
|||
if (!Array.isArray(tags)) { |
|||
tags = [ tags ]; |
|||
} |
|||
|
|||
const streams = getStreamsByTags(tags); |
|||
for (let i = 0; i < streams.length; i++) { |
|||
const s = streams[i]; |
|||
|
|||
if (s.stream) { |
|||
s.stream.write(output); |
|||
|
|||
} else if (s.type === "http") { |
|||
httpRequest(s, output); |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = { log, logt }; |
|||
|
@ -0,0 +1,39 @@ |
|||
{ |
|||
"file": [ |
|||
{ |
|||
"tags": "all" |
|||
} |
|||
], |
|||
"console": [ |
|||
{ |
|||
"tags": "error", |
|||
"stdstream": "stderr" |
|||
}, |
|||
{ |
|||
"tags": "warn", |
|||
"stdstream": "stdout" |
|||
}, |
|||
{ |
|||
"tags": "info", |
|||
"stdstream": "stdout" |
|||
}, |
|||
{ |
|||
"tags": "verbose", |
|||
"stdstream": "stdout" |
|||
}, |
|||
{ |
|||
"tags": [ "all", "log" ], |
|||
"stdstream": "stdout" |
|||
} |
|||
], |
|||
"http": [ |
|||
{ |
|||
"tags": "none", |
|||
"url": "https://localhost:8181", |
|||
"method": "POST", |
|||
"headers": { |
|||
"content-type": "application/json" |
|||
} |
|||
} |
|||
] |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue