diff --git a/docs/data-source-options.md b/docs/data-source-options.md index 036090a3f..ab8f1e2f1 100644 --- a/docs/data-source-options.md +++ b/docs/data-source-options.md @@ -56,7 +56,7 @@ Different RDBMS-es have their own specific options. You can also specify different types of logging to be enabled, for example `["query", "error", "schema"]`. Learn more about [Logging](logging.md). -- `logger` - Logger to be used for logging purposes. Possible values are "advanced-console", "simple-console" and "file". +- `logger` - Logger to be used for logging purposes. Possible values are "advanced-console", "formatted-console", "simple-console" and "file". Default is "advanced-console". You can also specify a logger class that implements `Logger` interface. Learn more about [Logging](logging.md). diff --git a/docs/logging.md b/docs/logging.md index e0558b36e..a671cf9ef 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -89,6 +89,8 @@ TypeORM ships with 4 different types of logger: and sql syntax highlighting. - `simple-console` - this is a simple console logger which is exactly the same as the advanced logger, but it does not use any color highlighting. This logger can be used if you have problems / or don't like colorized logs. +- `formatted-console` - this is almost the same as the advanced logger, but it formats sql queries to + be more readable (using [@sqltools/formatter](https://github.com/mtxr/vscode-sqltools)). - `file` - this logger writes all logs into `ormlogs.log` in the root folder of your project (near `package.json`). - `debug` - this logger uses [debug package](https://github.com/visionmedia/debug), to turn on logging set your env variable `DEBUG=typeorm:*` (note logging option has no effect on this logger). @@ -131,7 +133,7 @@ export class MyCustomLogger extends AbstractLogger { ) { const messages = this.prepareLogMessages(logMessage, { highlightSql: false, - }) + }, queryRunner) for (let message of messages) { switch (message.type ?? level) { diff --git a/src/data-source/BaseDataSourceOptions.ts b/src/data-source/BaseDataSourceOptions.ts index 03aa17d0b..c467d05ed 100644 --- a/src/data-source/BaseDataSourceOptions.ts +++ b/src/data-source/BaseDataSourceOptions.ts @@ -77,6 +77,7 @@ export interface BaseDataSourceOptions { readonly logger?: | "advanced-console" | "simple-console" + | "formatted-console" | "file" | "debug" | Logger diff --git a/src/index.ts b/src/index.ts index cb7a3af37..d082c0c0d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -103,6 +103,7 @@ export * from "./logger/AbstractLogger" export * from "./logger/Logger" export * from "./logger/LoggerOptions" export * from "./logger/AdvancedConsoleLogger" +export * from "./logger/FormattedConsoleLogger" export * from "./logger/SimpleConsoleLogger" export * from "./logger/FileLogger" export * from "./metadata/EntityMetadata" diff --git a/src/logger/AbstractLogger.ts b/src/logger/AbstractLogger.ts index 5c581bc8e..61412fb5e 100644 --- a/src/logger/AbstractLogger.ts +++ b/src/logger/AbstractLogger.ts @@ -293,12 +293,14 @@ export abstract class AbstractLogger implements Logger { | number | (LogMessage | string | number)[], options?: Partial, + queryRunner?: QueryRunner, ): LogMessage[] { options = { ...{ addColonToPrefix: true, appendParameterAsComment: true, highlightSql: true, + formatSql: false, }, ...options, } @@ -314,6 +316,13 @@ export abstract class AbstractLogger implements Logger { if (message.format === "sql") { let sql = String(message.message) + if (options.formatSql) { + sql = PlatformTools.formatSql( + sql, + queryRunner?.connection?.options.type, + ) + } + if ( options.appendParameterAsComment && message.parameters && diff --git a/src/logger/FormattedConsoleLogger.ts b/src/logger/FormattedConsoleLogger.ts new file mode 100644 index 000000000..9a82e01b0 --- /dev/null +++ b/src/logger/FormattedConsoleLogger.ts @@ -0,0 +1,72 @@ +import { PlatformTools } from "../platform/PlatformTools" +import { AbstractLogger } from "./AbstractLogger" +import { LogLevel, LogMessage } from "./Logger" +import { QueryRunner } from "../query-runner/QueryRunner" + +/** + * Performs logging of the events in TypeORM. + * This version of logger uses console to log events, syntax highlighting and formatting. + */ +export class FormattedConsoleLogger extends AbstractLogger { + /** + * Write log to specific output. + */ + protected writeLog( + level: LogLevel, + logMessage: LogMessage | LogMessage[], + queryRunner?: QueryRunner, + ) { + const messages = this.prepareLogMessages( + logMessage, + { + highlightSql: true, + formatSql: true, + }, + queryRunner, + ) + + for (let message of messages) { + switch (message.type ?? level) { + case "log": + case "schema-build": + case "migration": + PlatformTools.log(String(message.message)) + break + + case "info": + case "query": + if (message.prefix) { + PlatformTools.logInfo(message.prefix, message.message) + } else { + PlatformTools.log(String(message.message)) + } + break + + case "warn": + case "query-slow": + if (message.prefix) { + PlatformTools.logWarn(message.prefix, message.message) + } else { + console.warn( + PlatformTools.warn(String(message.message)), + ) + } + break + + case "error": + case "query-error": + if (message.prefix) { + PlatformTools.logError( + message.prefix, + String(message.message), + ) + } else { + console.error( + PlatformTools.error(String(message.message)), + ) + } + break + } + } + } +} diff --git a/src/logger/Logger.ts b/src/logger/Logger.ts index 0f7ba9c78..bb6725430 100644 --- a/src/logger/Logger.ts +++ b/src/logger/Logger.ts @@ -98,6 +98,7 @@ export type LogMessageType = */ export type PrepareLogMessagesOptions = { highlightSql: boolean + formatSql: boolean appendParameterAsComment: boolean addColonToPrefix: boolean } diff --git a/src/logger/LoggerFactory.ts b/src/logger/LoggerFactory.ts index cca0998ec..9e6c51d4e 100644 --- a/src/logger/LoggerFactory.ts +++ b/src/logger/LoggerFactory.ts @@ -5,6 +5,7 @@ import { AdvancedConsoleLogger } from "./AdvancedConsoleLogger" import { FileLogger } from "./FileLogger" import { DebugLogger } from "./DebugLogger" import { ObjectUtils } from "../util/ObjectUtils" +import { FormattedConsoleLogger } from "./FormattedConsoleLogger" /** * Helps to create logger instances. @@ -17,6 +18,7 @@ export class LoggerFactory { logger?: | "advanced-console" | "simple-console" + | "formatted-console" | "file" | "debug" | Logger, @@ -35,6 +37,9 @@ export class LoggerFactory { case "advanced-console": return new AdvancedConsoleLogger(options) + case "formatted-console": + return new FormattedConsoleLogger(options) + case "debug": return new DebugLogger() } diff --git a/src/platform/PlatformTools.ts b/src/platform/PlatformTools.ts index 4d7b8baa1..1ecb7fb36 100644 --- a/src/platform/PlatformTools.ts +++ b/src/platform/PlatformTools.ts @@ -3,6 +3,9 @@ import dotenv from "dotenv" import fs from "fs" import path from "path" import { highlight } from "sql-highlight" +import { format as sqlFormat } from "@sqltools/formatter" +import { type Config as SqlFormatterConfig } from "@sqltools/formatter/lib/core/types" +import { type DatabaseType } from "../driver/types/DatabaseType" export { EventEmitter } from "events" export { ReadStream } from "fs" @@ -220,6 +223,27 @@ export class PlatformTools { }) } + /** + * Pretty-print sql string to be print in the console. + */ + static formatSql(sql: string, dataSourceType?: DatabaseType): string { + const databaseLanguageMap: Record< + string, + SqlFormatterConfig["language"] + > = { + oracle: "pl/sql", + } + + const databaseLanguage = dataSourceType + ? databaseLanguageMap[dataSourceType] || "sql" + : "sql" + + return sqlFormat(sql, { + language: databaseLanguage, + indent: " ", + }) + } + /** * Logging functions needed by AdvancedConsoleLogger */ diff --git a/test/github-issues/1738/issue-1738.ts b/test/github-issues/1738/issue-1738.ts new file mode 100644 index 000000000..e0849e632 --- /dev/null +++ b/test/github-issues/1738/issue-1738.ts @@ -0,0 +1,28 @@ +import sinon from "sinon" +import { PlatformTools } from "../../../src/platform/PlatformTools" +import { FormattedConsoleLogger } from "../../../src" +import { FORMAT_SQL_TEST_CASES } from "./queries" + +describe("github issues > #1738 Add FormattedConsoleLogger", () => { + let logger: FormattedConsoleLogger + let logInfoStub: sinon.SinonStub + let highlightStub: sinon.SinonStub + + beforeEach(() => { + logInfoStub = sinon.stub(PlatformTools, "logInfo") + highlightStub = sinon.stub(PlatformTools, "highlightSql") + highlightStub.callsFake((sql: string) => sql) + logger = new FormattedConsoleLogger("all") + }) + + afterEach(() => { + logInfoStub.restore() + highlightStub.restore() + }) + FORMAT_SQL_TEST_CASES.forEach((testCase) => { + it(`formats sql query: ${testCase.unformatted}`, () => { + logger.logQuery(testCase.unformatted) + sinon.assert.calledWith(logInfoStub, "query:", testCase.formatted) + }) + }) +}) diff --git a/test/github-issues/1738/queries.ts b/test/github-issues/1738/queries.ts new file mode 100644 index 000000000..e91dd334b --- /dev/null +++ b/test/github-issues/1738/queries.ts @@ -0,0 +1,33 @@ +export const CASE_1_UNFORMATTED = `SELECT * FROM tbl` +export const CASE_1_FORMATTED = `SELECT * +FROM tbl` + +export const CASE_2_UNFORMATTED = `SELECT tbl1.col1, tbl2.col2 FROM tbl1 INNER JOIN tbl2 ON tbl1.id = tbl2.id` +export const CASE_2_FORMATTED = `SELECT tbl1.col1, + tbl2.col2 +FROM tbl1 + INNER JOIN tbl2 ON tbl1.id = tbl2.id` + +export const CASE_3_UNFORMATTED = `SELECT col1, (SELECT col2 FROM tbl2 WHERE tbl2.id = tbl1.id) AS sub_col FROM tbl1` +export const CASE_3_FORMATTED = `SELECT col1, + ( + SELECT col2 + FROM tbl2 + WHERE tbl2.id = tbl1.id + ) AS sub_col +FROM tbl1` + +export const FORMAT_SQL_TEST_CASES = [ + { + unformatted: CASE_1_UNFORMATTED, + formatted: CASE_1_FORMATTED, + }, + { + unformatted: CASE_2_UNFORMATTED, + formatted: CASE_2_FORMATTED, + }, + { + unformatted: CASE_3_UNFORMATTED, + formatted: CASE_3_FORMATTED, + }, +] diff --git a/test/utils/test-utils.ts b/test/utils/test-utils.ts index edb344245..2a32582cc 100644 --- a/test/utils/test-utils.ts +++ b/test/utils/test-utils.ts @@ -154,6 +154,7 @@ export interface TestingOptions { createLogger?: () => | "advanced-console" | "simple-console" + | "formatted-console" | "file" | "debug" | Logger