@othree.io/journal
v2.0.0
Published
Journal
Readme
@othree.io/journal
Automatic input/output logging for TypeScript functions. Wrap any function with logIO or logIOAsync to get structured logging of arguments, return values, errors, and durations without manual console.log statements.
Includes built-in PII redaction and a pre-configured Winston logger.
Install
npm install @othree.io/journalPeer dependencies:
npm install @othree.io/scribe @othree.io/optional winston logform fast-redactThe package ships as dual CJS/ESM. Node's exports field resolves require() to CommonJS and import to ES modules automatically.
Usage
Wrapping a synchronous function
import { logIO } from '@othree.io/journal'
const add = (a: number, b: number) => a + b
const loggedAdd = logIO({
now: Date.now,
log: console.log,
logError: console.error
})(add)('add')
const result = loggedAdd(2, 3)
// Logs: ["add::Input", 2, 3]
// Logs: ["add::Output", 5, "add::Output::Finished in 1ms"]
result.get() // 5logIO returns an Optional<T>. Errors thrown by the wrapped function are captured rather than re-thrown:
const riskyFn = (x: number) => {
if (x < 0) throw new Error('negative')
return x * 2
}
const result = logIO({
now: Date.now,
log: console.log,
logError: console.error
})(riskyFn)('riskyFn')(-1)
// Logs: ["riskyFn::Input", -1]
// Logs: "riskyFn::Error"
// Logs: Error('negative')
// Logs: "riskyFn::Error::Failed in 0ms"
result.isPresent // false
result.getError() // Error('negative')Wrapping an async function
import { logIOAsync } from '@othree.io/journal'
const fetchUser = async (id: string) => { /* ... */ }
const loggedFetch = logIOAsync({
now: Date.now,
log: console.log,
logError: console.error
})(fetchUser)('fetchUser')
const result = await loggedFetch('user-123')
result.get() // the user objectControlling what gets logged
Pass a LoggingProps object as the second argument to the context:
const loggedFn = logIO({ now, log, logError })(fn)('myFn', {
logInput: true,
logOutput: false,
logErrors: true
})Custom log format
Pass a LogFormat object in the deps to customize log labels:
import { logIO, LogFormat } from '@othree.io/journal'
const format: LogFormat = {
inputLabel: (context) => `[${context}] IN`,
outputLabel: (context) => `[${context}] OUT`,
outputDurationLabel: (context, durationMs) => `[${context}] OUT took ${durationMs}ms`,
errorLabel: (context) => `[${context}] ERR`,
errorDurationLabel: (context, durationMs) => `[${context}] ERR took ${durationMs}ms`
}
const loggedFn = logIO({
now: Date.now,
log: console.log,
logError: console.error,
format
})(myFn)('myFn')
// Logs: ["[myFn] IN", ...args]
// Logs: ["[myFn] OUT", result, "[myFn] OUT took 5ms"]Redacting log wrapper
For non-Winston use cases, wrap any log function with redaction using withRedactingLog:
import { withRedactingLog, withRedaction } from '@othree.io/journal'
import fastRedact from 'fast-redact'
const redact = withRedaction({ env: process.env, fastRedact })({
type: 'Regex',
regexes: []
})
const redactingLog = withRedactingLog(redact)(console.log)
redactingLog({ email: '[email protected]', name: 'John' })
// Logs: { email: 'REDACTED_EMAILADDRESS', name: 'John' }Winston logger with redaction
Create a Winston logger that automatically redacts PII from log messages:
import { winston, withRedaction } from '@othree.io/journal'
import fastRedact from 'fast-redact'
const logger = winston.createWinstonLogger({
consoleOutput: console,
withRedaction: withRedaction({
env: process.env,
fastRedact: fastRedact
})
})('my-service', 'info', {
type: 'Regex',
regexes: []
})
logger.info({ email: '[email protected]', name: 'John' })
// Output: {"level":"info","message":{"email":"REDACTED_EMAILADDRESS","name":"John"},"service":"my-service"}Convenience Winston factory
For quick setup without manually wiring dependencies:
import { createDefaultWinstonLogger } from '@othree.io/journal'
const logger = createDefaultWinstonLogger(console)('my-service', 'info', {
type: 'Regex',
regexes: []
})
logger.info({ email: '[email protected]' })Regex redaction
Redacts values matching built-in patterns (credit cards, emails, phone numbers, SSNs, IP addresses, street addresses, usernames, passwords, credentials) plus any custom regexes you provide:
const logger = winston.createWinstonLogger({
consoleOutput: console,
withRedaction: withRedaction({ env: process.env, fastRedact })
})('my-service', 'info', {
type: 'Regex',
regexes: [], // additional custom regexes
replacement: 'HIDDEN' // optional, defaults to 'REDACTED'
})Path redaction
Redacts specific object paths using fast-redact:
const logger = winston.createWinstonLogger({
consoleOutput: console,
withRedaction: withRedaction({ env: process.env, fastRedact })
})('my-service', 'info', {
type: 'Path',
paths: ['password', 'user.ssn']
})Bypass redaction
Pass { JOURNAL_REDACT_BYPASS: 'true' } in the env dependency of withRedaction to disable all redaction (useful for local development):
const logger = winston.createWinstonLogger({
consoleOutput: console,
withRedaction: withRedaction({
env: { JOURNAL_REDACT_BYPASS: 'true' },
fastRedact
})
})('my-service', 'info')API
logIO(deps)(fn)(context, loggingProps?)
Wraps a synchronous function with automatic I/O logging.
| Parameter | Type | Description |
|---|---|---|
| deps.now | () => number | Clock function for duration measurement (e.g. Date.now) |
| deps.log | (entry: unknown) => void | Logger for input/output entries |
| deps.logError | (entry: unknown) => void | Logger for error entries |
| deps.format | LogFormat | Optional. Custom label format (default: Context::Input / ::Output / ::Error) |
| fn | (...args) => T | The function to wrap |
| context | string | Label used in log messages |
| loggingProps | LoggingProps | Optional. Controls which phases are logged (default: all true) |
Returns: (...args) => Optional<T>
logIOAsync(deps)(fn)(context, loggingProps?)
Same as logIO but for async functions. Returns (...args) => Promise<Optional<T>>.
LogFormat
Custom label format for log messages.
| Property | Type | Description |
|---|---|---|
| inputLabel | (context: string) => string | Label for input log entries |
| outputLabel | (context: string) => string | Label for output log entries |
| outputDurationLabel | (context: string, durationMs: number) => string | Duration label for output entries |
| errorLabel | (context: string) => string | Label for error log entries |
| errorDurationLabel | (context: string, durationMs: number) => string | Duration label for error entries |
withRedactingLog(redact)(log)
Wraps a log function with redaction.
| Parameter | Type | Description |
|---|---|---|
| redact | Redact | A redaction function (from withRedaction or noRedaction) |
| log | (entry: unknown) => void | The log function to wrap |
Returns: (entry: unknown) => void
createDefaultWinstonLogger(consoleOutput)(service, level, redactOptions?)
Convenience factory that creates a Winston logger with real dependencies (process.env, fast-redact) pre-wired.
| Parameter | Type | Description |
|---|---|---|
| consoleOutput | ConsoleOutput | Object with log, debug, info, warn, error methods |
| service | string | Service name included in log metadata |
| level | string | Minimum log level (debug, info, warn, error, etc.) |
| redactOptions | RedactorOptions | Optional. Regex or path-based redaction config |
winston.createWinstonLogger(deps)(service, level, redactOptions?)
Creates a Winston logger with JSON formatting, error stack traces, and optional PII redaction.
| Parameter | Type | Description |
|---|---|---|
| deps.consoleOutput | ConsoleOutput | Object with log, debug, info, warn, error methods |
| deps.withRedaction | (redactOptions?) => Redact | Redaction factory (see withRedaction) |
| service | string | Service name included in log metadata |
| level | string | Minimum log level (debug, info, warn, error, etc.) |
| redactOptions | RedactorOptions | Optional. Regex or path-based redaction config |
withRedaction(deps)(redactOptions?)
Creates a redaction function based on the provided options.
| Parameter | Type | Description |
|---|---|---|
| deps.env | { JOURNAL_REDACT_BYPASS?: string } | Environment variables for bypass control |
| deps.fastRedact | (opts) => (data) => string | The fast-redact library function |
| redactOptions | RedactorOptions | Optional. Regex or path-based redaction config |
Returns: Redact — a function (data: any) => any
Development
npm install
npm test # run tests
npx vitest run --coverage # run tests with coverage
npm run build # compile TypeScriptLicense
ISC
