-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f0534d1
commit 7b26cfb
Showing
5 changed files
with
302 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
}), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "@urban-ui/tsconfig/bun.json" | ||
} |