Skip to content

Commit

Permalink
Merge pull request #3 from aarontravass/aaron/logger
Browse files Browse the repository at this point in the history
feat: added log file streaming
  • Loading branch information
v 1 r t l authored May 31, 2023
2 parents 719ceb7 + 6454359 commit aff46b8
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 17 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
node_modules
dist
coverage
coverage
log
*.tsbuildinfo
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,43 @@ new App()
.listen(3000)
```

To Log a level, use the enum `LogLevel`

```ts
import { App } from '@tinyhttp/app'
import { logger, LogLevel } from '@tinyhttp/logger'

new App()
.use(
logger({
methods: ['GET', 'POST'],
timestamp: { format: 'HH:mm:ss' },
output: { callback: console.log, color: false, level: LogLevel.warn }
})
)
.get('/', (req, res) => res.send('Hello world'))
.listen(3000)
```

This also includes a simple file logger. To stream to a file, simply supply the filename in the options. Supported file names innclude
`./file.log` or `./log/tiny.log`

```ts
import { App } from '@tinyhttp/app'
import { logger } from '@tinyhttp/logger'

new App()
.use(
logger({
methods: ['GET', 'POST'],
timestamp: { format: 'HH:mm:ss' },
output: { callback: console.log, color: false, filename: './log/tiny.log' }
})
)
.get('/', (req, res) => res.send('Hello world'))
.listen(3000)
```

## Alternatives

- [Pino](https://getpino.io) - super fast, all natural json logger.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"prettier": "^2.8.8",
"rollup": "^3.23.0",
"supertest-fetch": "^1.5.0",
"tslib": "^2.5.2",
"tsm": "^2.3.0",
"typescript": "^5.0.4",
"uvu": "^0.5.6"
Expand Down
12 changes: 10 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 67 additions & 0 deletions src/filelogger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { accessSync, writeFileSync, createWriteStream, WriteStream, mkdirSync } from 'fs'
import { dirname as directoryname } from 'path'

export class FileLogger {
readonly #filename: string
readonly #dirname: string
private writableStream: WriteStream
constructor(filename: string) {
this.#dirname = directoryname(filename)
this.#filename = filename
this.#_stat()
this.#_createWritableStream()
this.#_endStream()
}

#fsAccess(filename: string, mode?: number) {
try {
accessSync(filename, mode)
return true
} catch (error) {
return false
}
}

#_stat() {
//check if file exists
if (!this.#fsAccess(this.#filename)) {
// check if directory exists
if (!this.#fsAccess(this.#dirname)) {
// create the directory
mkdirSync(this.#dirname, { recursive: true })
}
// create the file and write an empty string to it
writeFileSync(this.#filename, '')
return
}
}

#_createWritableStream() {
this.writableStream = createWriteStream(this.#filename, { flags: 'a' })
}

toFile(stringToLog: string) {
this.writableStream.write(stringToLog + '\n')
}

#_endStream() {
process.on('exit', () => {
this.writableStream.close()
})

process.on('SIGTERM', () => {
this.writableStream.close()
process.exit(0)
})

process.on('SIGINT', () => {
this.writableStream.close()
process.exit(0)
})

process.on('uncaughtException', () => {
this.writableStream.close()
process.exit(1)
})
}
}
38 changes: 28 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ import { cyan, red, magenta, bold } from 'colorette'
import statusEmoji from 'http-status-emojis'
import dayjs from 'dayjs'
import { METHODS, ServerResponse as Response, IncomingMessage as Request } from 'http'
import { FileLogger } from './filelogger'

export enum LogLevel {
error = 'error',
warn = 'warn',
trace = 'trace',
info = 'info',
log = 'log'
}

export interface LoggerOptions {
methods?: string[]
output?: {
color: boolean
filename?: string
callback: (string: string) => void
level?: LogLevel
}
timestamp?: boolean | { format?: string }
emoji?: boolean
Expand All @@ -24,12 +35,12 @@ const compileArgs = (
) => {
const { method } = req
const { statusCode } = res

const url = req.originalUrl || req.url

const methods = options.methods ?? METHODS
const timestamp = options.timestamp ?? false
const emojiEnabled = options.emoji
const level = options.output && options.output.level ? options.output.level : null
if (level) args.push('[' + level.toUpperCase() + ']')

if (methods.includes(method) && timestamp) {
args.push(
Expand All @@ -52,35 +63,42 @@ const compileArgs = (

export const logger = (options: LoggerOptions = {}) => {
const methods = options.methods ?? METHODS
const output = options.output ?? { callback: console.log, color: true }

const output = options.output ?? { callback: console.log, color: true, level: null }
let filelogger = null
if (options.output && options.output.filename) {
filelogger = new FileLogger(options.output.filename)
}
return (req: Request, res: Response, next?: () => void) => {
res.on('finish', () => {
const args: (string | number)[] = []

// every time
if (methods.includes(req.method)) {
const s = res.statusCode.toString()

let stringToLog = ''
if (!output.color) {
compileArgs(args, req, res, options)
const m = args.join(' ')
output.callback(m)
stringToLog = m
} else {
switch (s[0]) {
case '2':
compileArgs(args, req, res, options, cyan(bold(s)), cyan(res.statusMessage))
output.callback(args.join(' '))
stringToLog = args.join(' ')
break
case '4':
compileArgs(args, req, res, options, red(bold(s)), red(res.statusMessage))
output.callback(args.join(' '))
stringToLog = args.join(' ')
break
case '5':
compileArgs(args, req, res, options, magenta(bold(s)), magenta(res.statusMessage))
output.callback(args.join(' '))
stringToLog = args.join(' ')
break
}
}
output.callback(stringToLog)
if (filelogger) {
filelogger.toFile(stringToLog)
}
}
})

Expand Down
Loading

0 comments on commit aff46b8

Please sign in to comment.