You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

229 lines
6.5 KiB

2 years ago
  1. const fs = require("fs");
  2. const util = require("util");
  3. const http = require("http");
  4. const https = require("https");
  5. function timestamp() {
  6. return new Date().toISOString();
  7. }
  8. function formatOutput(...args) {
  9. return `${timestamp()} - ${util.format(...args)}\n`;
  10. }
  11. function parseStreams(config) {
  12. if (config.file) {
  13. for (let i = 0; i < config.file.length; i++) {
  14. const file = config.file[i];
  15. let path = file.path || `${__dirname}/${timestamp()}-log.txt`;
  16. try {
  17. file.stream = fs.createWriteStream(path, { flags: "a", autoClose: true });
  18. } catch (e) {
  19. console.error(`failed to open file at: ${path} for writing. Check the path in the config, and permissions for that directory and this process`);
  20. process.exit(1);
  21. }
  22. }
  23. }
  24. if (config.console) {
  25. for (let i = 0; i < config.console.length; i++) {
  26. const tty = config.console[i];
  27. if (
  28. !(tty.stdstream === "stdout"
  29. || tty.stdstream === "stderr"
  30. || tty.stdstream === "stdin")
  31. ) {
  32. 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.")
  33. process.exit(1);
  34. }
  35. tty.stream = process[tty.stdstream];
  36. }
  37. }
  38. if (config.http) {
  39. for (let i = 0; i < config.http.length; i++) {
  40. const h = config.http[i];
  41. if (!h.method) {
  42. h.method = "POST";
  43. } else if (h.method === "GET") {
  44. 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`);
  45. }
  46. if (!h.url) {
  47. 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`);
  48. process.exit(1);
  49. }
  50. if (!h.headers || !h.headers["content-type"]) {
  51. h.headers = {
  52. "content-type": "text/plain"
  53. };
  54. }
  55. }
  56. }
  57. }
  58. function loadAndParseConfig(configFilePath) {
  59. let config;
  60. try {
  61. const fileContents = fs.readFileSync(configFilePath, { encoding: "utf-8", flag: "r" });
  62. try {
  63. config = JSON.parse(fileContents);
  64. } catch (e) {
  65. console.error(`invalid JSON in your configuration file: ${configFilePath}`);
  66. process.exit(1);
  67. }
  68. } catch(e) {
  69. // no config file, warn about it, make a default
  70. console.warn(`no configuration file found at: ${configFilePath}. Using a default config.`);
  71. config = {
  72. "file": [
  73. {
  74. "tags": "all"
  75. },
  76. ],
  77. "console": [
  78. {
  79. "tags": "error",
  80. "stdstream": "stderr"
  81. },
  82. {
  83. "tags": "warn",
  84. "stdstream": "stdout"
  85. },
  86. {
  87. "tags": "info",
  88. "stdstream": "stdout"
  89. },
  90. {
  91. "tags": "verbose",
  92. "stdstream": "stdout"
  93. },
  94. {
  95. "tags": [ "all", "log" ],
  96. "stdstream": "stdout"
  97. }
  98. ],
  99. "http": [
  100. {
  101. "tags": "none",
  102. "url": "http://localhost:8080"
  103. }
  104. ]
  105. };
  106. }
  107. parseStreams(config);
  108. return config;
  109. }
  110. const configFilePath = __dirname + "/log4jesus.json";
  111. const config = loadAndParseConfig(configFilePath);
  112. function getStreamsOfTypeAndTags(tags, type) {
  113. const streams = config[type];
  114. const out = [];
  115. for (let i = 0; i < streams.length; i++) {
  116. const stream = streams[i];
  117. if (!Array.isArray(stream.tags)) {
  118. stream.tags = [ stream.tags ];
  119. }
  120. for (let j = 0; j < stream.tags.length; j++) {
  121. for (let k = 0; k < tags.length; k++) {
  122. if (stream.tags[j] === tags[k]) {
  123. out.push({ type, ...stream });
  124. }
  125. }
  126. }
  127. }
  128. return out;
  129. }
  130. function getStreamsByTags(tags) {
  131. if (!Array.isArray(tags)) {
  132. tags = [ tags ];
  133. }
  134. return [].concat(
  135. getStreamsOfTypeAndTags(tags, "file"),
  136. getStreamsOfTypeAndTags(tags, "console"),
  137. getStreamsOfTypeAndTags(tags, "http")
  138. );
  139. }
  140. // convienent list of streams that we always want to send our logs to, regardless of tags
  141. const alwaysStreams = getStreamsByTags("all");
  142. function httpRequest(options, payload) {
  143. const url = new URL(options.url);
  144. const client = url.protocol === "https" ? https : http;
  145. options.headers["content-length"] = payload.length;
  146. const request = client.request(url, options, response => {
  147. response.on("data", data => {
  148. // @TODO, if you care what the server responds with
  149. });
  150. });
  151. request.on("error", error => {
  152. // @TODO, if you care about errors on the request
  153. });
  154. request.write(payload);
  155. request.end();
  156. }
  157. // log by itself will only log to streams tagged 'all'. if you want to specifiy tags, call 'logt' instead
  158. function log(...args) {
  159. const output = formatOutput(...args);
  160. for (let i = 0; i < alwaysStreams.length; i++) {
  161. const s = alwaysStreams[i];
  162. if (s.stream) {
  163. s.stream.write(output);
  164. } else if (s.type === "http") {
  165. httpRequest(s, output);
  166. }
  167. }
  168. }
  169. // |tags| should be a string or array of strings identifying a key in the above configuration object
  170. // which informs the logger 'where' to output the log
  171. function logt(tags, ...args) {
  172. if (!args) {
  173. log(tags);
  174. return;
  175. }
  176. if (!Array.isArray(tags)) {
  177. tags = [ tags ];
  178. }
  179. const streams = getStreamsByTags(tags);
  180. for (let i = 0; i < streams.length; i++) {
  181. const s = streams[i];
  182. if (s.stream) {
  183. s.stream.write(output);
  184. } else if (s.type === "http") {
  185. httpRequest(s, output);
  186. }
  187. }
  188. }
  189. module.exports = { log, logt };