parse-log.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { registerTSPaths } from '../server/helpers/register-ts-paths'
  2. registerTSPaths()
  3. import { program } from 'commander'
  4. import { createReadStream, readdir } from 'fs-extra'
  5. import { join } from 'path'
  6. import { createInterface } from 'readline'
  7. import * as winston from 'winston'
  8. import { labelFormatter, mtimeSortFilesDesc } from '../server/helpers/logger'
  9. import { CONFIG } from '../server/initializers/config'
  10. import { inspect } from 'util'
  11. import { format as sqlFormat } from 'sql-formatter'
  12. program
  13. .option('-l, --level [level]', 'Level log (debug/info/warn/error)')
  14. .option('-f, --files [file...]', 'Files to parse. If not provided, the script will parse the latest log file from config)')
  15. .option('-t, --tags [tags...]', 'Display only lines with these tags')
  16. .option('-nt, --not-tags [tags...]', 'Donrt display lines containing these tags')
  17. .parse(process.argv)
  18. const options = program.opts()
  19. const excludedKeys = {
  20. level: true,
  21. message: true,
  22. splat: true,
  23. timestamp: true,
  24. tags: true,
  25. label: true,
  26. sql: true
  27. }
  28. function keysExcluder (key, value) {
  29. return excludedKeys[key] === true ? undefined : value
  30. }
  31. const loggerFormat = winston.format.printf((info) => {
  32. let additionalInfos = JSON.stringify(info, keysExcluder, 2)
  33. if (additionalInfos === '{}') additionalInfos = ''
  34. else additionalInfos = ' ' + additionalInfos
  35. if (info.sql) {
  36. if (CONFIG.LOG.PRETTIFY_SQL) {
  37. additionalInfos += '\n' + sqlFormat(info.sql, {
  38. language: 'sql',
  39. indent: ' '
  40. })
  41. } else {
  42. additionalInfos += ' - ' + info.sql
  43. }
  44. }
  45. return `[${info.label}] ${toTimeFormat(info.timestamp)} ${info.level}: ${info.message}${additionalInfos}`
  46. })
  47. const logger = winston.createLogger({
  48. transports: [
  49. new winston.transports.Console({
  50. level: options.level || 'debug',
  51. stderrLevels: [],
  52. format: winston.format.combine(
  53. winston.format.splat(),
  54. labelFormatter(),
  55. winston.format.colorize(),
  56. loggerFormat
  57. )
  58. })
  59. ],
  60. exitOnError: true
  61. })
  62. const logLevels = {
  63. error: logger.error.bind(logger),
  64. warn: logger.warn.bind(logger),
  65. info: logger.info.bind(logger),
  66. debug: logger.debug.bind(logger)
  67. }
  68. run()
  69. .then(() => process.exit(0))
  70. .catch(err => console.error(err))
  71. function run () {
  72. return new Promise<void>(async res => {
  73. const files = await getFiles()
  74. for (const file of files) {
  75. if (file === 'peertube-audit.log') continue
  76. console.log('Opening %s.', file)
  77. const stream = createReadStream(file)
  78. const rl = createInterface({
  79. input: stream
  80. })
  81. rl.on('line', line => {
  82. try {
  83. const log = JSON.parse(line)
  84. if (options.tags && !containsTags(log.tags, options.tags)) {
  85. return
  86. }
  87. if (options.notTags && containsTags(log.tags, options.notTags)) {
  88. return
  89. }
  90. // Don't know why but loggerFormat does not remove splat key
  91. Object.assign(log, { splat: undefined })
  92. logLevels[log.level](log)
  93. } catch (err) {
  94. console.error('Cannot parse line.', inspect(line))
  95. throw err
  96. }
  97. })
  98. stream.once('close', () => res())
  99. }
  100. })
  101. }
  102. // Thanks: https://stackoverflow.com/a/37014317
  103. async function getNewestFile (files: string[], basePath: string) {
  104. const sorted = await mtimeSortFilesDesc(files, basePath)
  105. return (sorted.length > 0) ? sorted[0].file : ''
  106. }
  107. async function getFiles () {
  108. if (options.files) return options.files
  109. const logFiles = await readdir(CONFIG.STORAGE.LOG_DIR)
  110. const filename = await getNewestFile(logFiles, CONFIG.STORAGE.LOG_DIR)
  111. return [ join(CONFIG.STORAGE.LOG_DIR, filename) ]
  112. }
  113. function toTimeFormat (time: string) {
  114. const timestamp = Date.parse(time)
  115. if (isNaN(timestamp) === true) return 'Unknown date'
  116. const d = new Date(timestamp)
  117. return d.toLocaleString() + `.${d.getMilliseconds()}`
  118. }
  119. function containsTags (loggerTags: string[], optionsTags: string[]) {
  120. if (!loggerTags) return false
  121. for (const lt of loggerTags) {
  122. for (const ot of optionsTags) {
  123. if (lt === ot) return true
  124. }
  125. }
  126. return false
  127. }