feat: add FormattedConsoleLogger (#11401)

* fix: Build ESM migrations for JS

Including jsdoc for typehinting
Add esm as an option in the migrate cli
Update the documentation for the JS migrations

Closes: #10801

* fix: Fix the migration documentation

* Cleanup the types in the migrations

* Add the formatted sql console

* Add the formatSql to the logger

* Update src/logger/FormattedConsoleLogger.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Make names in test more consistent

* Add a sub query example

* Set the language to the sql formatter

* Import SqlLanguage as a type from sql-formatter

* Remove empty console log

* Remove console log

* Add the dataSourceType

* Remove js extension from import

* Use another package to format the SQL in the logging

Same package as we use to generate the migrations

* Not need to add all the spaces in the log

* Fix the expected formatted queries

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Pieter Wigboldus 2025-04-16 18:55:23 +02:00 committed by GitHub
parent fe71a0c3e4
commit 4c8fc3a7cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 179 additions and 2 deletions

View File

@ -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).

View File

@ -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) {

View File

@ -77,6 +77,7 @@ export interface BaseDataSourceOptions {
readonly logger?:
| "advanced-console"
| "simple-console"
| "formatted-console"
| "file"
| "debug"
| Logger

View File

@ -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"

View File

@ -293,12 +293,14 @@ export abstract class AbstractLogger implements Logger {
| number
| (LogMessage | string | number)[],
options?: Partial<PrepareLogMessagesOptions>,
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 &&

View File

@ -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
}
}
}
}

View File

@ -98,6 +98,7 @@ export type LogMessageType =
*/
export type PrepareLogMessagesOptions = {
highlightSql: boolean
formatSql: boolean
appendParameterAsComment: boolean
addColonToPrefix: boolean
}

View File

@ -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()
}

View File

@ -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
*/

View File

@ -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)
})
})
})

View File

@ -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,
},
]

View File

@ -154,6 +154,7 @@ export interface TestingOptions {
createLogger?: () =>
| "advanced-console"
| "simple-console"
| "formatted-console"
| "file"
| "debug"
| Logger