Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import * as cookieParser from 'cookie-parser'
import * as cors from 'cors'
import * as https from 'https'
import * as http from 'http'
import logger, { logging, sphinxLogger } from './src/utils/logger'
import logger, {
installConsoleTimestampPrefix,
logging,
sphinxLogger,
} from './src/utils/logger'
import { checkInvitesHubInterval } from './src/hub'
import { genUsersInterval } from './src/utils/proxy'
import {
Expand All @@ -29,6 +33,7 @@ import rateLimit from 'express-rate-limit'

// force UTC time
process.env.TZ = 'UTC'
installConsoleTimestampPrefix()

const env = process.env.NODE_ENV || 'development'
const config = loadConfig()
Expand Down
37 changes: 37 additions & 0 deletions src/tests/unit_tests/logger.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
jest.mock('../../utils/config', () => ({
loadConfig: () => ({
logging: '',
logging_level: 'info',
}),
}))

import { timestampConsoleArgs } from '../../utils/logger'

describe('tests for src/utils/logger.ts', () => {
const timestamp = new Date(2026, 5, 14, 12, 0, 0)

test('prefixes string console messages with a timestamp', () => {
const args = timestampConsoleArgs(['hello', 'world'], timestamp)

expect(args).toStrictEqual(['-> 2026-06-14 12:00:00: hello', 'world'])
})

test('adds a timestamp argument before non-string console messages', () => {
const data = { ok: true }
const args = timestampConsoleArgs([data], timestamp)

expect(args).toStrictEqual(['-> 2026-06-14 12:00:00:', data])
})

test('does not prefix existing express logger timestamps again', () => {
const args = ['-> 2026-06-14 12:00:00: POST /messages 200 71ms']

expect(timestampConsoleArgs(args, timestamp)).toBe(args)
})

test('does not prefix existing sphinx logger timestamps again', () => {
const args = ['26-06-14T12:00:00', '[MISC]', 'hello']

expect(timestampConsoleArgs(args, timestamp)).toBe(args)
})
})
36 changes: 35 additions & 1 deletion src/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,40 @@ const config = loadConfig()

const blgrLogger = new blgr(config.logging_level)
const tsFormat = (ts) => moment(ts).format('YYYY-MM-DD HH:mm:ss').trim()
const consoleTimestampFlag = '__sphinxConsoleTimestampsInstalled'
const consoleMethods: Array<'debug' | 'error' | 'info' | 'log' | 'warn'> = [
'debug',
'error',
'info',
'log',
'warn',
]
const timestampedConsoleLineRegex =
/^(?:\x1b\[[0-9;]*m)*(?:-> \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}:|\d{2}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})/

function alreadyTimestamped(arg: any): boolean {
return typeof arg === 'string' && timestampedConsoleLineRegex.test(arg)
}

function timestampConsoleArgs(args: any[], ts: Date = new Date()): any[] {
if (args.length > 0 && alreadyTimestamped(args[0])) return args

const prefix = `-> ${tsFormat(ts)}:`
if (typeof args[0] === 'string') return [`${prefix} ${args[0]}`, ...args.slice(1)]

return [prefix, ...args]
}

function installConsoleTimestampPrefix() {
const consoleState = console as Console & { [key: string]: boolean | undefined }
if (consoleState[consoleTimestampFlag]) return

consoleState[consoleTimestampFlag] = true
consoleMethods.forEach((method) => {
const original = console[method].bind(console)
console[method] = (...args: any[]) => original(...timestampConsoleArgs(args))
})
}

const logger = expressWinston.logger({
transports: [new winston.transports.Console()],
Expand Down Expand Up @@ -132,4 +166,4 @@ const sphinxLogger = {
spam: sphinxLoggerSpam,
}

export { logging, sphinxLogger }
export { installConsoleTimestampPrefix, logging, sphinxLogger, timestampConsoleArgs }