parse-log.ts 4.1 KB

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