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.

211 lines
6.0 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. const fs = require("fs");
  2. const util = require("util");
  3. const http = require("http");
  4. const https = require("https");
  5. const crypto = require("crypto");
  6. function timestamp() {
  7. return new Date().toISOString();
  8. }
  9. function formatOutput(...args) {
  10. return `${timestamp()} - ${util.format(...args)}\n`;
  11. }
  12. function parseStreams(config) {
  13. if (config.file) {
  14. for (let i = 0; i < config.file.length; i++) {
  15. const file = config.file[i];
  16. file.id = crypto.randomBytes(8).toString("hex");
  17. let path = file.path || `${__dirname}/data/${timestamp()}.log`;
  18. try {
  19. file.stream = fs.createWriteStream(path, { flags: "a", autoClose: true });
  20. } catch (e) {
  21. console.error(`failed to open file at: ${path} for writing. Check the path in the config, and permissions for that directory and this process`);
  22. process.exit(1);
  23. }
  24. }
  25. }
  26. if (config.console) {
  27. for (let i = 0; i < config.console.length; i++) {
  28. const tty = config.console[i];
  29. if (
  30. !(tty.stdstream === "stdout"
  31. || tty.stdstream === "stderr"
  32. || tty.stdstream === "stdin")
  33. ) {
  34. 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.")
  35. process.exit(1);
  36. }
  37. tty.id = tty.stdstream;
  38. tty.stream = process[tty.stdstream];
  39. }
  40. }
  41. if (config.http) {
  42. for (let i = 0; i < config.http.length; i++) {
  43. const h = config.http[i];
  44. h.id = crypto.randomBytes(8).toString("hex");
  45. if (!h.method) {
  46. h.method = "POST";
  47. } else if (h.method === "GET") {
  48. console.warn(`using the http method 'GET' to send your logs is not recommended. Unfortunately, sending payloads with a GET request can be unreliable.\nSee:\nhttps://github.com/whatwg/fetch/issues/551#issue-235203784\nRemove this line in tlog.js to suppress this warning.`);
  49. }
  50. if (!h.url) {
  51. 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`);
  52. process.exit(1);
  53. }
  54. if (!h.headers || !h.headers["content-type"]) {
  55. h.headers = {
  56. "content-type": "text/plain"
  57. };
  58. }
  59. if (!h.errorTags) {
  60. h.errorTags = [];
  61. } else if (!Array.isArray(h.errorTags)) {
  62. h.errorTags = [ h.errorTags ];
  63. }
  64. }
  65. }
  66. return config;
  67. }
  68. const config = parseStreams(require("./tlog.conf.js"));
  69. function getStreamsOfTypeAndTags(tags, type) {
  70. const streams = config[type];
  71. const out = [];
  72. for (let i = 0; i < streams.length; i++) {
  73. const stream = streams[i];
  74. if (!Array.isArray(stream.tags)) {
  75. stream.tags = [ stream.tags ];
  76. }
  77. for (let j = 0; j < stream.tags.length; j++) {
  78. for (let k = 0; k < tags.length; k++) {
  79. if (stream.tags[j] === tags[k]) {
  80. out.push({ type, ...stream });
  81. }
  82. }
  83. }
  84. }
  85. return out;
  86. }
  87. function getStreamsByTags(tags) {
  88. return [].concat(
  89. getStreamsOfTypeAndTags(tags, "file"),
  90. getStreamsOfTypeAndTags(tags, "console"),
  91. getStreamsOfTypeAndTags(tags, "http")
  92. );
  93. }
  94. // convienent list of streams that we always want to send our logs to, regardless of tags
  95. const alwaysStreams = getStreamsByTags(["all"]);
  96. function httpRequest(options, ...args) {
  97. const url = new URL(options.url);
  98. const client = url.protocol === "https" ? https : http;
  99. const payload = formatOutput(...args);
  100. options.headers["content-length"] = payload.length;
  101. const request = client.request(url, options, response => {
  102. response.on("data", data => {
  103. // @TODO, if you care what the server responds with
  104. });
  105. });
  106. request.on("error", error => {
  107. const streams = getStreamsByTags(options.errorTags);
  108. const usedStreams = {};
  109. for (let i = 0; i < streams.length; i++) {
  110. const stream = streams[i];
  111. if (usedStreams[stream.id]) {
  112. continue;
  113. }
  114. usedStreams[stream.id] = true;
  115. if (stream.id !== options.id) {
  116. write(stream, "error writing to http: ", ...args);
  117. }
  118. }
  119. });
  120. request.write(formatOutput(...args));
  121. request.end();
  122. }
  123. function write(s, ...args) {
  124. if (s.stream) {
  125. s.stream.write(formatOutput(...args));
  126. } else if (s.type === "http") {
  127. httpRequest(s, ...args);
  128. }
  129. }
  130. // log by itself will only log to streams tagged 'all'. if you want to specifiy tags, call 'logt' instead
  131. function log(...args) {
  132. for (let i = 0; i < alwaysStreams.length; i++) {
  133. write(alwaysStreams[i], ...args);
  134. }
  135. }
  136. // |tags| should be a string or array of strings identifying a key in the above configuration object
  137. // which informs the logger 'where' to output the log
  138. function logt(tags, ...args) {
  139. if (!args) {
  140. return;
  141. }
  142. if (!Array.isArray(tags)) {
  143. tags = [ tags ];
  144. }
  145. const usedStreams = {};
  146. const streams = getStreamsByTags(tags);
  147. for (let i = 0; i < streams.length; i++) {
  148. const s = streams[i];
  149. if (usedStreams[s.id]) {
  150. continue;
  151. }
  152. write(s, ...args);
  153. usedStreams[s.id] = true;
  154. }
  155. // now send it to the 'all' streams. we need to check if we've already done the job, because in the case a stream is tagged with both 'all' and another tag, we'd double send if we didn't de-dupe.
  156. for (let i = 0; i < alwaysStreams.length; i++) {
  157. const s = alwaysStreams[i];
  158. if (usedStreams[s.id]) {
  159. continue;
  160. }
  161. write(s, ...args);
  162. usedStreams[s.id] = true;
  163. }
  164. }
  165. module.exports = { log, logt };