diff --git a/.github/workflows/foam-cli.yml b/.github/workflows/foam-cli.yml index 8134a552..b8e48a53 100644 --- a/.github/workflows/foam-cli.yml +++ b/.github/workflows/foam-cli.yml @@ -19,8 +19,11 @@ jobs: - name: Install dependencies run: yarn - # - name: Lint foam-lint - # run: yarn workspace foam-cli lint + - name: Lint foam-lint + run: yarn workspace foam-cli lint + + - name: Build foam-core + run: yarn workspace foam-core build - name: Test foam-cli run: yarn workspace foam-cli test diff --git a/.github/workflows/foam-vscode.yml b/.github/workflows/foam-vscode.yml index 280a202c..c1db406a 100644 --- a/.github/workflows/foam-vscode.yml +++ b/.github/workflows/foam-vscode.yml @@ -22,6 +22,10 @@ jobs: - name: Lint foam-vscode run: yarn workspace foam-vscode lint + + - name: Build foam-core + run: yarn workspace foam-core build + - name: Test foam-vscode run: yarn workspace foam-vscode test # - name: Publish foam-vscode diff --git a/packages/foam-cli/src/commands/janitor.ts b/packages/foam-cli/src/commands/janitor.ts index 9ef04484..6547ec34 100644 --- a/packages/foam-cli/src/commands/janitor.ts +++ b/packages/foam-cli/src/commands/janitor.ts @@ -42,7 +42,6 @@ export default class Janitor extends Command { if (isValidDirectory(workspacePath)) { const config = createConfigFromFolders([workspacePath]); const services: Services = { - logger: console, dataStore: new FileDataStore(config), }; const graph = (await bootstrap(config, services)).notes; diff --git a/packages/foam-cli/src/commands/migrate.ts b/packages/foam-cli/src/commands/migrate.ts index 505fd2b5..3f5e23eb 100644 --- a/packages/foam-cli/src/commands/migrate.ts +++ b/packages/foam-cli/src/commands/migrate.ts @@ -46,7 +46,6 @@ Successfully generated link references and heading! if (isValidDirectory(workspacePath)) { const services: Services = { - logger: console, dataStore: new FileDataStore(config), }; let graph = (await bootstrap(config, services)).notes; diff --git a/packages/foam-core/src/index.ts b/packages/foam-core/src/index.ts index c42de6f3..a4da9372 100644 --- a/packages/foam-core/src/index.ts +++ b/packages/foam-core/src/index.ts @@ -2,10 +2,11 @@ import { Note, NoteLink, URI } from './types'; import { NoteGraph, NoteGraphAPI } from './note-graph'; import { FoamConfig } from './config'; import { IDataStore, FileDataStore } from './services/datastore'; -import { ILogger } from './services/logger'; +import { ILogger } from './utils/log'; export { IDataStore, FileDataStore }; export { ILogger }; +export { LogLevel, LogLevelThreshold, Logger, BaseLogger } from './utils/log'; export { IDisposable, isDisposable } from './common/lifecycle'; export { Event, Emitter } from './common/event'; export { FoamConfig }; @@ -37,7 +38,6 @@ export { export interface Services { dataStore: IDataStore; - logger: ILogger; } export interface Foam { diff --git a/packages/foam-core/src/markdown-provider.ts b/packages/foam-core/src/markdown-provider.ts index df4bd85b..51868d38 100644 --- a/packages/foam-core/src/markdown-provider.ts +++ b/packages/foam-core/src/markdown-provider.ts @@ -19,6 +19,7 @@ import { } from './utils'; import { ID } from './types'; import { ParserPlugin } from './plugins'; +import { Logger } from './utils/log'; const tagsPlugin: ParserPlugin = { name: 'tags', @@ -87,7 +88,7 @@ const handleError = ( e: Error ): void => { const name = plugin.name || ''; - console.warn( + Logger.warn( `Error while executing [${fnName}] in plugin [${name}] for file [${uri}]`, e ); @@ -117,6 +118,7 @@ export function createMarkdownParser(extraPlugins: ParserPlugin[]): NoteParser { const foamParser: NoteParser = { parse: (uri: string, markdown: string): Note => { + Logger.debug('Parsing:', uri); markdown = plugins.reduce((acc, plugin) => { try { return plugin.onWillParseMarkdown?.(acc) || acc; @@ -176,7 +178,7 @@ export function createMarkdownParser(extraPlugins: ParserPlugin[]): NoteParser { } } } catch (e) { - console.warn(`Error while parsing YAML for [${uri}]`, e); + Logger.warn(`Error while parsing YAML for [${uri}]`, e); } } @@ -195,6 +197,7 @@ export function createMarkdownParser(extraPlugins: ParserPlugin[]): NoteParser { handleError(plugin, 'onDidVisitTree', uri, e); } }); + Logger.debug('Result:', note); return note; }, }; @@ -260,7 +263,7 @@ export function createMarkdownReferences( if (!target) { const candidates = graph.getNotes({ slug: link.link.slug }); if (candidates.length > 1) { - console.log( + Logger.info( `Warning: Slug ${link.link.slug} matches ${candidates.length} documents. Picking one.` ); } @@ -269,7 +272,7 @@ export function createMarkdownReferences( // We are dropping links to non-existent notes here, // but int the future we may want to surface these too if (!target) { - console.log( + Logger.info( `Warning: Link '${link.to}' in '${noteId}' points to a non-existing note.` ); return null; diff --git a/packages/foam-core/src/plugins/index.ts b/packages/foam-core/src/plugins/index.ts index b8771045..ba5e00ef 100644 --- a/packages/foam-core/src/plugins/index.ts +++ b/packages/foam-core/src/plugins/index.ts @@ -6,6 +6,7 @@ import { Middleware } from '../note-graph'; import { Note } from '../types'; import unified from 'unified'; import { FoamConfig } from '../config'; +import { Logger } from '../utils/log'; export interface FoamPlugin { name: string; @@ -47,10 +48,11 @@ export async function loadPlugins(config: FoamConfig): Promise { try { const pluginFile = path.join(dir, 'index.js'); fs.accessSync(pluginFile); + Logger.info(`Found plugin at [${pluginFile}]. Loading..`); const plugin = validate(await import(pluginFile)); return plugin; } catch (e) { - console.error(`Error while loading plugin at [${dir}] - skipping`, e); + Logger.error(`Error while loading plugin at [${dir}] - skipping`, e); return null; } }) diff --git a/packages/foam-core/src/services/datastore.ts b/packages/foam-core/src/services/datastore.ts index 52e75c8b..28d108ae 100644 --- a/packages/foam-core/src/services/datastore.ts +++ b/packages/foam-core/src/services/datastore.ts @@ -5,6 +5,7 @@ import fs from 'fs'; import { Event, Emitter } from '../common/event'; import { URI } from '../types'; import { FoamConfig } from '../config'; +import { Logger } from '../utils/log'; const findAllFiles = promisify(glob); @@ -83,6 +84,10 @@ export class FileDataStore implements IDataStore { ignoreGlobs.push(...config.ignoreGlobs.map(withFolder)); }); + Logger.debug('Glob patterns', { + includeGlobs, + ignoreGlobs, + }); this.match = (files: URI[]) => { return micromatch(files, includeGlobs, { ignore: ignoreGlobs, diff --git a/packages/foam-core/src/services/logger.ts b/packages/foam-core/src/services/logger.ts deleted file mode 100644 index 3b28781c..00000000 --- a/packages/foam-core/src/services/logger.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface ILogger { - log(message?: any, ...optionalParams: any[]): void; - debug(message?: any, ...optionalParams: any[]): void; - info(message?: any, ...optionalParams: any[]): void; - warn(message?: any, ...optionalParams: any[]): void; - error(message?: any, ...optionalParams: any[]): void; -} diff --git a/packages/foam-core/src/utils/log.ts b/packages/foam-core/src/utils/log.ts new file mode 100644 index 00000000..62627051 --- /dev/null +++ b/packages/foam-core/src/utils/log.ts @@ -0,0 +1,89 @@ +export interface ILogger { + debug(message?: any, ...params: any[]): void; + info(message?: any, ...params: any[]): void; + warn(message?: any, ...params: any[]): void; + error(message?: any, ...params: any[]): void; + getLevel(): LogLevelThreshold; + setLevel(level: LogLevelThreshold): void; +} + +export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; +export type LogLevelThreshold = LogLevel | 'off'; + +export abstract class BaseLogger implements ILogger { + private static severity = { + debug: 1, + info: 2, + warn: 3, + error: 4, + }; + + constructor(private level: LogLevelThreshold = 'info') {} + + abstract log(lvl: LogLevel, msg?: any, ...extra: any[]): void; + + doLog(msgLevel: LogLevel, message?: any, ...params: any[]): void { + if (this.level === 'off') { + return; + } + if (BaseLogger.severity[msgLevel] >= BaseLogger.severity[this.level]) { + this.log(msgLevel, message, ...params); + } + } + + debug(message?: any, ...params: any[]): void { + this.doLog('debug', message, ...params); + } + info(message?: any, ...params: any[]): void { + this.doLog('info', message, ...params); + } + warn(message?: any, ...params: any[]): void { + this.doLog('warn', message, ...params); + } + error(message?: any, ...params: any[]): void { + this.doLog('error', message, ...params); + } + getLevel(): LogLevelThreshold { + return this.level; + } + setLevel(level: LogLevelThreshold): void { + this.level = level; + } +} + +export class ConsoleLogger extends BaseLogger { + log(level: LogLevel, msg?: string, ...params: any[]): void { + console[level](`[${level}] ${msg}`, ...params); + } +} + +export class NoOpLogger extends BaseLogger { + log(_l: LogLevel, _m?: string, ..._p: any[]): void {} +} + +export class Logger { + static debug(message?: any, ...params: any[]): void { + Logger.defaultLogger.debug(message, ...params); + } + static info(message?: any, ...params: any[]): void { + Logger.defaultLogger.info(message, ...params); + } + static warn(message?: any, ...params: any[]): void { + Logger.defaultLogger.warn(message, ...params); + } + static error(message?: any, ...params: any[]): void { + Logger.defaultLogger.error(message, ...params); + } + static getLevel(): LogLevelThreshold { + return Logger.defaultLogger.getLevel(); + } + static setLevel(level: LogLevelThreshold): void { + Logger.defaultLogger.setLevel(level); + } + + private static defaultLogger: ILogger = new ConsoleLogger(); + + static setDefaultLogger(logger: ILogger) { + Logger.defaultLogger = logger; + } +} diff --git a/packages/foam-core/test/janitor/generateHeadings.test.ts b/packages/foam-core/test/janitor/generateHeadings.test.ts index c3ec0f5d..d39392ad 100644 --- a/packages/foam-core/test/janitor/generateHeadings.test.ts +++ b/packages/foam-core/test/janitor/generateHeadings.test.ts @@ -14,7 +14,6 @@ describe('generateHeadings', () => { ]); const services: Services = { dataStore: new FileDataStore(config), - logger: console, }; const foam = await bootstrap(config, services); _graph = foam.notes; diff --git a/packages/foam-core/test/janitor/generateLinkReferences.test.ts b/packages/foam-core/test/janitor/generateLinkReferences.test.ts index ce4e4254..bff7cc65 100644 --- a/packages/foam-core/test/janitor/generateLinkReferences.test.ts +++ b/packages/foam-core/test/janitor/generateLinkReferences.test.ts @@ -15,7 +15,6 @@ describe('generateLinkReferences', () => { ]); const services: Services = { dataStore: new FileDataStore(config), - logger: console, }; _graph = await bootstrap(config, services).then(foam => foam.notes); }); diff --git a/packages/foam-vscode/package.json b/packages/foam-vscode/package.json index 95f13e0b..c8046bca 100644 --- a/packages/foam-vscode/package.json +++ b/packages/foam-vscode/package.json @@ -40,6 +40,10 @@ ] }, "commands": [ + { + "command": "foam-vscode.set-log-level", + "title": "Foam: Set log level" + }, { "command": "foam-vscode.show-graph", "title": "Foam: Show graph" @@ -80,6 +84,17 @@ ], "description": "Specifies the list of globs that will be ignored by Foam (e.g. they will not be considered when creating the graph). To ignore the all the content of a given folder, use `/**/*`" }, + "foam.logging.level": { + "type": "string", + "default": "info", + "enum": [ + "off", + "debug", + "info", + "warn", + "error" + ] + }, "foam.edit.linkReferenceDefinitions": { "type": "string", "default": "withoutExtensions", diff --git a/packages/foam-vscode/src/extension.ts b/packages/foam-vscode/src/extension.ts index dc205f7d..bf6f1c82 100644 --- a/packages/foam-vscode/src/extension.ts +++ b/packages/foam-vscode/src/extension.ts @@ -8,16 +8,24 @@ import { Foam, FileDataStore, Services, - isDisposable + isDisposable, + Logger } from "foam-core"; import { features } from "./features"; import { getConfigFromVscode } from "./services/config"; +import { VsCodeOutputLogger, exposeLogger } from "./services/logging"; let foam: Foam | null = null; export async function activate(context: ExtensionContext) { + const logger = new VsCodeOutputLogger(); + Logger.setDefaultLogger(logger); + exposeLogger(context, logger); + try { + Logger.info("Starting Foam"); + const config: FoamConfig = getConfigFromVscode(); const dataStore = new FileDataStore(config); @@ -39,7 +47,6 @@ export async function activate(context: ExtensionContext) { }); const services: Services = { - logger: console, dataStore: dataStore }; const foamPromise: Promise = bootstrap(config, services); @@ -49,8 +56,9 @@ export async function activate(context: ExtensionContext) { }); foam = await foamPromise; + Logger.info(`Loaded ${foam.notes.getNotes().length} notes`); } catch (e) { - console.log("An error occurred while bootstrapping Foam", e); + Logger.error("An error occurred while bootstrapping Foam", e); window.showErrorMessage( `An error occurred while bootstrapping Foam. ${e.stack}` ); diff --git a/packages/foam-vscode/src/services/logging.ts b/packages/foam-vscode/src/services/logging.ts new file mode 100644 index 00000000..91e9ccb5 --- /dev/null +++ b/packages/foam-vscode/src/services/logging.ts @@ -0,0 +1,56 @@ +import { window, commands, ExtensionContext } from "vscode"; +import { ILogger, IDisposable, LogLevel, BaseLogger } from "foam-core"; +import { getFoamLoggerLevel } from "../settings"; + +export interface VsCodeLogger extends ILogger, IDisposable { + show(); +} + +export class VsCodeOutputLogger extends BaseLogger implements VsCodeLogger { + private channel = window.createOutputChannel("Foam"); + + constructor() { + super(getFoamLoggerLevel()); + this.channel.appendLine("Foam Logging: " + getFoamLoggerLevel()); + } + + log(lvl: LogLevel, msg?: any, ...extra: any[]): void { + if (msg) { + this.channel.appendLine( + `[${lvl} - ${new Date().toLocaleTimeString()}] ${msg}` + ); + } + extra?.forEach(param => { + if (param?.stack) { + this.channel.appendLine(JSON.stringify(param.stack, null, 2)); + } else { + this.channel.appendLine(JSON.stringify(param, null, 2)); + } + }); + } + + show() { + this.channel.show(); + } + dispose(): void { + this.channel.dispose(); + } +} + +export const exposeLogger = ( + context: ExtensionContext, + logger: VsCodeLogger +): void => { + context.subscriptions.push( + commands.registerCommand("foam-vscode.set-log-level", async () => { + const items: LogLevel[] = ["debug", "info", "warn", "error"]; + const level = await window.showQuickPick( + items.map(item => ({ + label: item, + description: item === logger.getLevel() && "Current" + })) + ); + logger.setLevel(level.label); + }) + ); +}; diff --git a/packages/foam-vscode/src/settings.ts b/packages/foam-vscode/src/settings.ts index 3383fc78..78d6f001 100644 --- a/packages/foam-vscode/src/settings.ts +++ b/packages/foam-vscode/src/settings.ts @@ -1,4 +1,5 @@ import { workspace } from "vscode"; +import { LogLevel } from "foam-core"; export enum LinkReferenceDefinitionsSetting { withExtensions = "withExtensions", @@ -24,3 +25,7 @@ export function getIgnoredFilesSetting(): string[] { export function getTitleMaxLength(): number { return workspace.getConfiguration("foam.graph").get("titleMaxLength"); } + +export function getFoamLoggerLevel(): LogLevel { + return workspace.getConfiguration("foam.logging").get("level") ?? "info"; +} diff --git a/packages/foam-vscode/src/utils.ts b/packages/foam-vscode/src/utils.ts index cc7117a5..422cffa1 100644 --- a/packages/foam-vscode/src/utils.ts +++ b/packages/foam-vscode/src/utils.ts @@ -10,6 +10,7 @@ import { Selection } from "vscode"; import * as fs from "fs"; +import { Logger } from "foam-core"; interface Point { line: number; @@ -28,7 +29,7 @@ export function loadDocConfig() { // Load workspace config let activeEditor = window.activeTextEditor; if (!activeEditor) { - console.log("Failed to load config, no active editor"); + Logger.debug("Failed to load config, no active editor"); return; }