Skip to content

Commit

Permalink
add 🚀 arc logging
Browse files Browse the repository at this point in the history
  • Loading branch information
mattstyles committed May 16, 2024
1 parent f0534d1 commit 7b26cfb
Show file tree
Hide file tree
Showing 5 changed files with 302 additions and 0 deletions.
41 changes: 41 additions & 0 deletions scripts/arc-log/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@urban-ui/arc-log",
"version": "0.2.0",
"description": "Logging specifically for arc",
"module": "./src/index.ts",
"types": "./src/index.ts",
"type": "module",
"license": "MIT",
"exports": {
".": {
"import": "./src/index.ts",
"types": "./src/index.ts"
}
},
"scripts": {
"test": "bun test",
"lint": "biome lint src"
},
"author": "Matt Styles",
"repository": {
"type": "git",
"url": "git+https://github.com/mattstyles/urban-ui.git",
"directory": "packages/scripts/arc-log"
},
"bugs": {
"url": "https://github.com/mattstyles/urban-ui/issues"
},
"devDependencies": {
"@types/bun": "latest",
"@types/debug": "^4.1.12",
"typescript": "^5.4.2"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"chalk": "^5.3.0",
"debug": "^4.3.4",
"type-fest": "^4.18.2"
}
}
Empty file added scripts/arc-log/readme.md
Empty file.
156 changes: 156 additions & 0 deletions scripts/arc-log/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import {
afterEach,
beforeEach,
describe,
expect,
mock,
spyOn,
test,
} from 'bun:test'
import chalk from 'chalk'

import { createLogger } from './index'

const globalLog = console.log
const globalWarn = console.warn
const globalError = console.error
beforeEach(() => {
console.log = mock()
console.warn = mock()
console.error = mock()
})
afterEach(() => {
console.log = globalLog
console.warn = globalWarn
console.error = globalError
})

describe('[createLogger] def log level', () => {
const log = createLogger('test', chalk.cyan)

test('log level should default to log output', () => {
log.log('hello')
expect(console.log).toHaveBeenCalled()
})

test('verbose should not output anything', () => {
log.verbose('verbose')
expect(console.log).not.toHaveBeenCalled()
})

test('debug should not output anything', () => {
log.debug('debug')
expect(console.log).not.toHaveBeenCalled()
})

test('warn should always output', () => {
log.warn('warn')
expect(console.log).not.toHaveBeenCalled()
expect(console.warn).toHaveBeenCalled()
})

test('error should always output', () => {
log.error('error')
expect(console.log).not.toHaveBeenCalled()
expect(console.error).toHaveBeenCalled()
})

test('Output assertions', () => {
const expected = {
namespace: 'namespace',
log: 'expected',
}

const spy = spyOn(console, 'log')
const log = createLogger(expected.namespace, chalk.blue)
log.log(expected.log)

expect(spy.mock.calls[0][0]).toContain(expected.namespace)
expect(spy.mock.calls[0][1]).toContain(expected.log)
})
})

/**
* Suite instantiates a logger in each test to ensure that the environment variable is set correctly
*/
describe('[createLogger] verbose log level', () => {
beforeEach(() => {
process.env.LOG_LEVEL = 'verbose'
})
afterEach(() => {
process.env.LOG_LEVEL = undefined
})

test('log', () => {
const log = createLogger('verbose', chalk.yellow)
log.log('called')
expect(console.log).toHaveBeenCalled()
})

test('verbose', () => {
const log = createLogger('verbose', chalk.yellow)
log.verbose('not called')
expect(console.log).toHaveBeenCalled()
})

test('debug should not output anything', () => {
const log = createLogger('verbose', chalk.yellow)
log.debug('debug')
expect(console.log).not.toHaveBeenCalled()
})

test('warn should always output', () => {
const log = createLogger('verbose', chalk.yellow)
log.warn('warn')
expect(console.log).not.toHaveBeenCalled()
expect(console.warn).toHaveBeenCalled()
})

test('error should always output', () => {
const log = createLogger('verbose', chalk.yellow)
log.error('error')
expect(console.log).not.toHaveBeenCalled()
expect(console.error).toHaveBeenCalled()
})
})

describe('[createLogger] CI log level', () => {
beforeEach(() => {
process.env.CI = 'true'
})
afterEach(() => {
process.env.CI = undefined
})

test('log', () => {
const log = createLogger('ci', chalk.yellow)
log.log('called')
expect(console.log).toHaveBeenCalled()
})

test('verbose', () => {
const log = createLogger('ci', chalk.yellow)
log.verbose('not called')
expect(console.log).not.toHaveBeenCalled()
})

test('debug', () => {
const log = createLogger('ci', chalk.yellow)
log.debug('debug')
expect(console.log).not.toHaveBeenCalled()
})

test('warn', () => {
const log = createLogger('verbose', chalk.yellow)
log.warn('warn')
expect(console.log).not.toHaveBeenCalled()
expect(console.warn).toHaveBeenCalled()
})

test('error', () => {
const log = createLogger('verbose', chalk.yellow)
log.error('error')
expect(console.log).not.toHaveBeenCalled()
expect(console.error).toHaveBeenCalled()
})
})
102 changes: 102 additions & 0 deletions scripts/arc-log/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import type { ChalkInstance } from 'chalk'
import chalk from 'chalk'
import createDebugger from 'debug'

type LogLevel = 'debug' | 'verbose' | 'log' | 'warning' | 'error'
const level: Record<LogLevel, number> = {
/**
* Debug logs are only shown when DEBUG=namespace or DEBUG=* is present in the environment
*/
debug: 10,
verbose: 20,
log: 30,
/**
* Warnings are always shown
*/
warning: 0,
/**
* Errors are always shown
*/
error: 0,
}

/**
* Uses LOGLEVEL, LOG_LEVEL, DEBUG, and CI from the environment.
* LOGLEVEL takes precendence over LOG_LEVEL, and defaults to log. It overrides CI.
* CI takes precendence as always sets the global level to log.
* DEBUG determines whether debug logs are shown.
*/
function getGlobalLevel(def: LogLevel = 'log'): number {
const gLevel: string | undefined = process.env.LOG_LEVEL
if (level[gLevel?.toLowerCase() as LogLevel] != null) {
return level[gLevel?.toLowerCase() as LogLevel]
}

if (process.env.CI != null && process.env.CI?.toLowerCase() === 'true') {
return level.log
}

return level[def]
}

type LogFnParams = Parameters<Console['log']>
type ConsoleLevel = Pick<Console, 'log' | 'debug' | 'warn' | 'error'>
type LoggerInstanceOptions = {
prefix: string
consoleLevel: keyof ConsoleLevel
}

function createInstance(opts: LoggerInstanceOptions) {
return function write(...args: LogFnParams) {
console[opts.consoleLevel](opts.prefix, ...args)
}
}

function noop(...args: LogFnParams) {
return
}

export type ArcLogFn = (...args: LogFnParams) => void
export type ArcLogger = {
debug: createDebugger.Debugger
verbose: ArcLogFn
log: ArcLogFn
warn: ArcLogFn
error: ArcLogFn
}

export function createLogger(
namespace: string,
colour: ChalkInstance,
): ArcLogger {
const globalLevel = getGlobalLevel()
const prefix = `${chalk.dim.bold('[')}${colour.bold(
namespace,
)}${chalk.dim.bold(']')}`

return {
debug: createDebugger(namespace),
verbose:
globalLevel <= level.verbose
? createInstance({
prefix: prefix,
consoleLevel: 'log',
})
: noop,
log:
globalLevel <= level.log
? createInstance({
prefix: prefix,
consoleLevel: 'log',
})
: noop,
warn: createInstance({
prefix: `${prefix} ⚠️`,
consoleLevel: 'warn',
}),
error: createInstance({
prefix: `${prefix} ❗️`,
consoleLevel: 'error',
}),
}
}
3 changes: 3 additions & 0 deletions scripts/arc-log/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "@urban-ui/tsconfig/bun.json"
}

0 comments on commit 7b26cfb

Please sign in to comment.