example @logger.* decorator usages
@logger.logClass()
export class SimplestExample {
//all methods except those matching ignorePattern will get wrapped with trace style logging
...
}
@logger.logClass()
export class LoginService {
@logger.transformArgs((username: string, password: string) => [username, '<redacted>']) //redact or modify specific args
async Login(username: string, password: string): Promise<boolean> {
...
}
@logger.transformArgs(logger.builtInTransforms.redacted) //alternative to redact all args
async SomeOtherLogin(username: string, password: string): Promise<boolean> {
}
...
}
@logger.logClass()
export class LockedSQLiteDBConnection {
txnId?: string;
constructor(private db: SQLiteDBConnection, private flush: () => Promise<void>, private txnLock = 'txn_lock', private loggingDisabled = false) {}
@logger.logIf((instance: LockedSQLiteDBConnection) => !instance.loggingDisabled) //filter whether to log based on properties of the class instance at log time
async executeSet(txnId: string, set: capSQLiteSet[]): Promise<capSQLiteChanges> {
...
}
@logger.logIf((instance: unknown, description: string, actions: any) => !description.startsWith('internal')) //...and/or by arguments
async txn(description: string, actions: (txnId: string) => Promise<void>): Promise<void> {
...
}
}
@logger.logClass()
export default class Home extends Vue {
...
@logger.logLevel('info') //increase log level for methods that signify more major events in application
async sync(): Promise<void> {
...
}
}
@logger.logClass((className, instance: TypedDocumentStore<never, never>) => `${className}<${instance.tableName}>`) //override the class name to include e.g. generics
export class TypedDocumentStore<T extends IdInterface, TIndex extends TIndexType<T>> implements ITypedDocumentStore {
...
@logger.transformResult((r: T[]) => r?.length) //transform logged results for specific cases where e.g. only the array *length* is relevant
async getAll(): Promise<T[]> {
...
}
}
//note no @logger.logClass()
export class MostlyInternalCalls {
//many internal functions, excluded from logging
...
@logger.logMethod('MostlyInternalCalls') //opt in to just this method, simpler than @logger.logClass and many @logger.ignore
public async function MethodRelevantToLog(): Promise<void> {
...
}
}
//logger.log to manually log one-off cases
async uploadAll() : Promise<void> {
try {
//some error-able calls here
} catch (e: unknown) {
if (e instanceof TypeError && e.message === 'Failed to fetch') {
//manually log the exceptional case here
logger.log.warn('Service Unavailable', { className: 'Uploader', methodName: 'uploadAll' });
//some code to handle error internally
...
}
}
}
see the end of src/logger.ts for default configuration/conventions, and builtInTransformers
export const configuration: IConfiguration = {
sinks: [{ name: 'default console log', write: async (logEntry: ILogEntry): Promise<void> => console[logEntry.logLevel](`warning - logging not configured - ${logEntry.message}`, logEntry.meta) }],
ignorePattern: fnName => fnName.startsWith('_'),
transformEachArg: builtInTransforms.arrayToLength(builtInTransforms.maxLength(1000)),
transformResult: builtInTransforms.arrayToLength(builtInTransforms.maxLength(1000)),
sinkErrorHandler: async (err: unknown, sinkName: string) => console.error(`error in sink ${sinkName}`, err),
};
example console sink:
import * as flatted_JSON from 'flatted';
import { logger } from './logger';
export const consoleSink: logger.ISinkWriter = async logEntry => {
const m = logEntry.meta;
console[logEntry.logLevel](m.timestamp, logEntry.indent, logEntry.message, m.className, `${m.methodName}(${flatted_JSON.stringify(m.args) ?? ''})`, flatted_JSON.stringify(m.result) ?? '');
};
example (capacitor) text file sink:
import * as C_JSON from 'flatted';
import { Filesystem, ReadFileOptions } from '@capacitor/filesystem';
import { logger } from './logger';
export const textFileSink = (options: ReadFileOptions): logger.ISinkWriter => <logger.ISinkWriter>(async logEntry => {
const m = logEntry.meta;
// eslint-disable-next-line max-len
const line = `${m.timestamp} ${logEntry.logLevel} ${(m.className ?? '').padEnd(30)} ${(m.methodName ?? '').padEnd(20)} ${logEntry.indent} ${(logEntry.message ?? '').padEnd(50)} (${flatted_JSON.stringify(m.args) ?? ''}) ${flatted_JSON.stringify(m.result) ?? ''}`;
await Filesystem.appendFile({ ...options, ...{ data: `${line}\n` } });
});
example log initialization
/* eslint-disable no-use-before-define */
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
import { logger, loggerConfiguration } from 'ts-logger';
import { textFileSink } from './sink_textFile';
import { consoleSink } from './sink_console';
const textLogFileOptions = { directory: Directory.Data, path: 'log.txt', encoding: Encoding.UTF8 };
const configuration : loggerConfiguration.ISinkLogLevelConfig[] = [
{
name: 'textFile',
out: textFileSink(textLogFileOptions),
minimumLevel: 'debug',
overrides: {
LoginService: 'warn',
},
},
{
name: 'console',
out: consoleSink,
minimumLevel: 'error',
},
];
async function init() {
await Filesystem.stat(textLogFileOptions).catch(() => Filesystem.writeFile({ ...textLogFileOptions, ...{ data: '' } })); //if log file doesn't exist, create it
logger.configuration.sinks = configuration.map(sink => loggerConfiguration.filteredSink(sink));
}
init();