index.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. 'use strict';
  2. var fs = require('fs');
  3. var path = require('path');
  4. var util = require('util');
  5. var EOL = require('os').EOL;
  6. var LEVELS = [ 'error', 'warn', 'info', 'verbose', 'debug', 'silly' ];
  7. /**
  8. * @param {string|Object} text
  9. * @param {...Object} [objects]
  10. * @name module.exports.error
  11. */
  12. /**
  13. * @param {string|Object} text
  14. * @param {...Object} [objects]
  15. * @name module.exports.warn
  16. */
  17. /**
  18. * @param {string|Object} text
  19. * @param {...Object} [objects]
  20. * @name module.exports.info
  21. */
  22. /**
  23. * @param {string|Object} text
  24. * @param {...Object} [objects]
  25. * @name module.exports.verbose
  26. */
  27. /**
  28. * @param {string|Object} text
  29. * @param {...Object} [objects]
  30. * @name module.exports.debug
  31. */
  32. /**
  33. * @param {string|Object} text
  34. * @param {...Object} [objects]
  35. * @name module.exports.silly
  36. */
  37. module.exports.log = log;
  38. module.exports.format = formatFile;
  39. /**
  40. * Set this variable if you doesn't specify app name in package.json
  41. * @type {undefined|string}
  42. */
  43. module.exports.appName = undefined;
  44. module.exports.transports = {};
  45. module.exports.transports.console = transportConsole;
  46. module.exports.transports.console.format = formatConsole;
  47. module.exports.transports.console.level = 'silly';
  48. module.exports.transports.file = transportFile;
  49. module.exports.transports.file.format = formatFile;
  50. module.exports.transports.file.level = 'warn';
  51. module.exports.transports.file.maxSize = 1024 * 1024;
  52. module.exports.transports.file.streamConfig = undefined;
  53. module.exports.findLogPath = findLogPath;
  54. for (var i = 0; i < LEVELS.length; i++) {
  55. module.exports[LEVELS[i]] = log.bind(module.exports, LEVELS[i]);
  56. }
  57. /**
  58. * @param {string} level
  59. * @param {string} text
  60. */
  61. function log(level, text) {
  62. var args = Array.prototype.slice.call(arguments, 1);
  63. args = args.map(function formatErrors(arg) {
  64. return arg instanceof Error ? arg.stack + EOL : arg;
  65. });
  66. text = util.format.apply(util, args);
  67. var msg = {
  68. level: level,
  69. text: text,
  70. date: new Date()
  71. };
  72. var transports = module.exports.transports;
  73. for (var i in transports) {
  74. // jshint -W089
  75. if (!transports.hasOwnProperty(i) || typeof transports[i] !== 'function') {
  76. continue;
  77. }
  78. if (!compareLevels(transports[i].level, level)) {
  79. continue;
  80. }
  81. transports[i].call(module.exports, msg);
  82. }
  83. }
  84. function compareLevels(passLevel, checkLevel) {
  85. var pass = LEVELS.indexOf(passLevel);
  86. var check = LEVELS.indexOf(checkLevel);
  87. if (check === -1 || pass === -1) {
  88. return true;
  89. }
  90. return check <= pass;
  91. }
  92. // region transport
  93. function transportConsole(msg) {
  94. var text = format(msg, transportConsole.format || module.exports.format);
  95. if (console[msg.level]) {
  96. console[msg.level](text);
  97. } else {
  98. console.log(text);
  99. }
  100. }
  101. function transportFile(msg) {
  102. var text = format(msg, transportFile.format || module.exports.format);
  103. if (undefined === transportFile.stream) {
  104. transportFile.file = transportFile.file || findLogPath(module.exports.appName);
  105. if (!transportFile.file) {
  106. transportFile.stream = false;
  107. log('warn', 'electron-log.transports.file: Could not set a log file');
  108. return;
  109. }
  110. if (transportFile.maxSize > 0) {
  111. logRotate(transportFile.file, transportFile.maxSize);
  112. }
  113. transportFile.stream = fs.createWriteStream(
  114. transportFile.file,
  115. transportFile.streamConfig || { flags: 'a' }
  116. );
  117. }
  118. if (!transportFile.stream) {
  119. return;
  120. }
  121. transportFile.stream.write(text + EOL);
  122. }
  123. function logRotate(file, maxSize) {
  124. try {
  125. const stat = fs.statSync(file);
  126. if (stat.size > maxSize) {
  127. fs.renameSync(file, file.replace(/log$/, 'old.log'));
  128. }
  129. } catch (e) {}
  130. }
  131. // endregion transport
  132. // region get log path
  133. /**
  134. * Try to determine a platform-specific path where can write logs
  135. * @param {string} [appName] App name, path-safe, loads by package.json by default
  136. * @return {string|boolean}
  137. */
  138. function findLogPath(appName) {
  139. appName = appName || findAppName();
  140. if (!appName) {
  141. return false;
  142. }
  143. var dir;
  144. switch (process.platform) {
  145. case 'linux':
  146. dir = prepareDir(process.env['XDG_CONFIG_HOME'], appName)
  147. .or(process.env['HOME'], '.config', appName)
  148. .or(process.env['XDG_DATA_HOME'], appName)
  149. .or(process.env['HOME'], '.local', 'share', appName)
  150. .result;
  151. break;
  152. case 'darwin':
  153. dir = prepareDir(process.env['HOME'], 'Library', 'Logs', appName)
  154. .or(process.env['HOME'], 'Library', 'Application Support', appName)
  155. .result;
  156. break;
  157. case 'win32':
  158. dir = prepareDir(process.env['APPDATA'], appName)
  159. .or(process.env['USERPROFILE'], 'AppData', 'Roaming', appName)
  160. .result;
  161. break;
  162. }
  163. if (dir) {
  164. return path.join(dir, 'log.log');
  165. } else {
  166. return false;
  167. }
  168. function findAppName() {
  169. var appName;
  170. try {
  171. var appPkg = loadAppPackage();
  172. if (!appPkg || (!appPkg.productName && !appPkg.name)) {
  173. transportFile.stream = false;
  174. log('warn', 'electron-log cannot read a name from package.json');
  175. return false;
  176. }
  177. appName = appPkg.productName || appPkg.name;
  178. } catch (e) {
  179. transportFile.stream = false;
  180. log('warn', 'electron-log: ' + e.message);
  181. return false;
  182. }
  183. return appName;
  184. }
  185. function prepareDir(dirPath) {
  186. // jshint -W040
  187. if (!this || this.or !== prepareDir || !this.result) {
  188. if (!dirPath) {
  189. return { or: prepareDir };
  190. }
  191. dirPath = path.join.apply(path, arguments);
  192. mkDir(dirPath);
  193. try {
  194. fs.accessSync(dirPath, fs.W_OK);
  195. } catch (e) {
  196. return { or: prepareDir };
  197. }
  198. }
  199. return {
  200. or: prepareDir,
  201. result: (this ? this.result : false) || dirPath
  202. };
  203. }
  204. function mkDir(dirPath, root) {
  205. var dirs = dirPath.split(path.sep);
  206. var dir = dirs.shift();
  207. root = (root || '') + dir + path.sep;
  208. try {
  209. fs.mkdirSync(root);
  210. }
  211. catch (e) {
  212. if (!fs.statSync(root).isDirectory()) {
  213. throw new Error(e);
  214. }
  215. }
  216. return !dirs.length || mkDir(dirs.join(path.sep), root);
  217. }
  218. }
  219. /**
  220. * Try to load main app package
  221. * @throws {Error}
  222. * @return {Object|null}
  223. */
  224. function loadAppPackage() {
  225. var packageFile;
  226. try {
  227. packageFile = find(path.dirname(require.main.filename));
  228. } catch (e) {}
  229. if (!packageFile && process.resourcesPath) {
  230. packageFile = find(path.join(process.resourcesPath, 'app.asar'));
  231. }
  232. if (!packageFile) {
  233. packageFile = find(process.cwd());
  234. }
  235. if (!packageFile) {
  236. return null;
  237. }
  238. var content = fs.readFileSync(packageFile, 'utf-8');
  239. return JSON.parse(content);
  240. function find(root) {
  241. var file;
  242. while (!file) {
  243. var parent;
  244. file = path.join(root, 'package.json');
  245. try {
  246. fs.statSync(file);
  247. } catch (e) {
  248. parent = path.resolve(root, '..');
  249. file = null;
  250. }
  251. if (root === parent) {
  252. break;
  253. }
  254. root = parent;
  255. }
  256. return file;
  257. }
  258. }
  259. // endregion get log path
  260. // region formatter
  261. function format(msg, formatter) {
  262. if (typeof formatter === 'function') {
  263. return formatter(msg);
  264. }
  265. var date = msg.date;
  266. return formatter
  267. .replace('{level}', msg.level)
  268. .replace('{text}', msg.text)
  269. .replace('{y}', date.getFullYear())
  270. .replace('{m}', pad(date.getMonth() + 1))
  271. .replace('{d}', pad(date.getDate()))
  272. .replace('{h}', pad(date.getHours()))
  273. .replace('{i}', pad(date.getMinutes()))
  274. .replace('{s}', pad(date.getSeconds()))
  275. .replace('{ms}', pad(date.getMilliseconds(), 4));
  276. }
  277. function formatConsole(msg) {
  278. var time =
  279. pad(msg.date.getHours()) + ':' +
  280. pad(msg.date.getMinutes()) + ':' +
  281. pad(msg.date.getSeconds()) + ':' +
  282. pad(msg.date.getMilliseconds(), 4);
  283. return '[' + time + '] [' + msg.level + '] ' + msg.text;
  284. }
  285. function formatFile(msg) {
  286. var date =
  287. msg.date.getFullYear() + '-' +
  288. pad(msg.date.getMonth() + 1) + '-' +
  289. pad(msg.date.getDate()) + ' ' +
  290. pad(msg.date.getHours()) + ':' +
  291. pad(msg.date.getMinutes()) + ':' +
  292. pad(msg.date.getSeconds()) + ':' +
  293. pad(msg.date.getMilliseconds(), 4);
  294. return '[' + date + '] [' + msg.level + '] ' + msg.text;
  295. }
  296. function pad(number, zeros) {
  297. zeros = zeros || 2;
  298. return (new Array(zeros + 1).join('0') + number).substr(-zeros, zeros);
  299. }
  300. // endregion formatter