diff --git a/docs/docs/logging.md b/docs/docs/logging.md index bacd944af9eb..806dd9a7e18b 100644 --- a/docs/docs/logging.md +++ b/docs/docs/logging.md @@ -73,6 +73,39 @@ For MongoDB you can use `MongoHighlighter` from `@mikro-orm/mongo-highlighter` p Several customization options exist to allow for style changes or custom logic. +### Query Labels + +It may often be beneficial to log the origin of a query when using [`EntityManager.find`](entity-manager.md#fetching-entities-with-entitymanager) or [`EntityManager.findOne`](entity-manager.md#fetching-entities-with-entitymanager) for debugging and redundancy elimination purposes. + +An optional `loggerContext` option can be included within the `FindOptions` parameter of either call which will add a label to the output when debug mode is enabled. + +```ts +const author = await em.findOne(Author, { id: 1 }, { loggerContext: { label: 'Author Retrieval - /authors/me' } }); +// [query] (Author Retrieval - /authors/me) select "a0".* from "Author" as "a0" where "a0"."id" = 1 limit 1 [took 21 ms] +``` + +### Changing `debugMode` or disabling logging for specific queries + +If you'd like to disable logging or change the debug mode on a per-query basis, you can leverage `FindOptions.logging` and its `enabled` or `debugMode` property: + +```ts +// MikroORM.init({ debug: true }); +const author = await em.findOne(Author, { id: 1 }, { logging: { enabled: false } }); +// Overrides config and displays no logger output + +// ... + +// MikroORM.init({ debug: false }); +const author = await em.findOne(Author, { id: 1 }, { logging: { enabled: true } }); +// Overrides config and displays logger output + +// ... + +// MikroORM.init({ debug: ['query-labels'] }); +const author = await em.findOne(Author, { id: 1 }, { logging: { debugMode: ['query'] } }); +// Overrides config and displays logger output for query +``` + ### Using a custom logger You can provide your own logger function via the `logger` option: @@ -142,7 +175,7 @@ interface Logger { warn(namespace: LoggerNamespace, message: string, context?: LogContext): void; logQuery(context: LogContext): void; setDebugMode(debugMode: boolean | LoggerNamespace[]): void; - isEnabled(namespace: LoggerNamespace): boolean; + isEnabled(namespace: LoggerNamespace, context?: LogContext): boolean; } type LoggerNamespace = 'query' | 'query-params' | 'schema' | 'discovery' | 'info'; @@ -153,6 +186,8 @@ interface LogContext extends Dictionary { params?: unknown[]; took?: number; level?: 'info' | 'warning' | 'error'; + enabled?: boolean; + debugMode?: LoggerNamespace[]; connection?: { type?: string; name?: string; @@ -160,18 +195,8 @@ interface LogContext extends Dictionary { } ``` -### Query Labels - -It may often be beneficial to log the origin of a query when using [`EntityManager.find`](entity-manager.md#fetching-entities-with-entitymanager) or [`EntityManager.findOne`](entity-manager.md#fetching-entities-with-entitymanager) for debugging and redundancy elimination purposes. - -An optional `loggerContext` option can be included within the `FindOptions` parameter of either call which will add a label to the output when debug mode is enabled. - -```ts -const author = await em.findOne(Author, { id: 1 }, { loggerContext: { label: 'Author Retrieval - /authors/me' } }); -// [query] (Author Retrieval - /authors/me) select "a0".* from "Author" as "a0" where "a0"."id" = 1 limit 1 [took 21 ms] -``` - ### Providing additional context to a custom logger + If you have implemented your own `LoggerFactory` and need to access additional contextual values inside your customer logger implementation, utilize the `LoggerContext` property of `FindOptions`. Adding additional key/value pairs to that object will make them available inside your custom logger: ```ts diff --git a/packages/core/src/drivers/IDatabaseDriver.ts b/packages/core/src/drivers/IDatabaseDriver.ts index dc596bc2aec7..6d6feb520990 100644 --- a/packages/core/src/drivers/IDatabaseDriver.ts +++ b/packages/core/src/drivers/IDatabaseDriver.ts @@ -10,7 +10,7 @@ import type { Collection } from '../entity/Collection'; import type { EntityManager } from '../EntityManager'; import type { DriverException } from '../exceptions'; import type { Configuration } from '../utils/Configuration'; -import type { LoggerContext } from '../logging'; +import type { LoggingOptions, LogContext } from '../logging'; export const EntityManagerType = Symbol('EntityManagerType'); @@ -139,7 +139,8 @@ export interface FindOptions extends Omit, 'limit' | 'offset'> { diff --git a/packages/core/src/errors.ts b/packages/core/src/errors.ts index 0f95e9bc76b9..3efb724aefea 100644 --- a/packages/core/src/errors.ts +++ b/packages/core/src/errors.ts @@ -103,7 +103,7 @@ export class ValidationError extends Error { static cannotRemoveFromCollectionWithoutOrphanRemoval(owner: AnyEntity, property: EntityProperty): ValidationError { const options = [ ' - add `orphanRemoval: true` to the collection options', - ' - add `onDelete: \'cascade\'` to the owning side options', + ' - add `deleteRule: \'cascade\'` to the owning side options', ' - add `nullable: true` to the owning side options', ].join('\n'); return new ValidationError(`Removing items from collection ${owner.constructor.name}.${property.name} without \`orphanRemoval: true\` would break non-null constraint on the owning side. You have several options: \n${options}`, owner); diff --git a/packages/core/src/logging/DefaultLogger.ts b/packages/core/src/logging/DefaultLogger.ts index 91aac04df354..3746fb8afb9d 100644 --- a/packages/core/src/logging/DefaultLogger.ts +++ b/packages/core/src/logging/DefaultLogger.ts @@ -14,7 +14,7 @@ export class DefaultLogger implements Logger { * @inheritDoc */ log(namespace: LoggerNamespace, message: string, context?: LogContext): void { - if (!this.isEnabled(namespace)) { + if (!this.isEnabled(namespace, context)) { return; } @@ -59,15 +59,18 @@ export class DefaultLogger implements Logger { this.debugMode = debugMode; } - isEnabled(namespace: LoggerNamespace): boolean { - return !!this.debugMode && (!Array.isArray(this.debugMode) || this.debugMode.includes(namespace)); + isEnabled(namespace: LoggerNamespace, context?: LogContext) { + if (context?.enabled !== undefined) { return context.enabled; } + const debugMode = context?.debugMode ?? this.debugMode; + + return !!debugMode && (!Array.isArray(debugMode) || debugMode.includes(namespace)); } /** * @inheritDoc */ logQuery(context: { query: string } & LogContext): void { - if (!this.isEnabled('query')) { + if (!this.isEnabled('query', context)) { return; } diff --git a/packages/core/src/logging/Logger.ts b/packages/core/src/logging/Logger.ts index 7af7d7e5603a..3812b100cd48 100644 --- a/packages/core/src/logging/Logger.ts +++ b/packages/core/src/logging/Logger.ts @@ -27,7 +27,7 @@ export interface Logger { */ setDebugMode(debugMode: boolean | LoggerNamespace[]): void; - isEnabled(namespace: LoggerNamespace): boolean; + isEnabled(namespace: LoggerNamespace, context?: LogContext): boolean; } @@ -40,6 +40,8 @@ export interface LogContext extends Dictionary { took?: number; results?: number; level?: 'info' | 'warning' | 'error'; + enabled?: boolean; + debugMode?: LoggerNamespace[]; connection?: { type?: string; name?: string; @@ -54,10 +56,12 @@ export interface LoggerOptions { } /** - * Context for a logger to utilize to format output, including a label and additional properties that can be accessed by custom loggers + * Logger options to modify format output and overrides, including a label and additional properties that can be accessed by custom loggers + * + * Differs from {@link LoggerOptions} in terms of how they are used; this type is primarily a public type meant to be used within methods like `EntityManager.Find` * * @example * await em.findOne(User, 1, { loggerContext: { label: 'user middleware' } }; * // [query] (user middleware) select * from user where id = 1; */ -export type LoggerContext = Pick & Dictionary; +export type LoggingOptions = Pick & Dictionary; diff --git a/packages/core/src/logging/SimpleLogger.ts b/packages/core/src/logging/SimpleLogger.ts index f8022e59d880..ad305ab5fdd3 100644 --- a/packages/core/src/logging/SimpleLogger.ts +++ b/packages/core/src/logging/SimpleLogger.ts @@ -10,7 +10,7 @@ export class SimpleLogger extends DefaultLogger { * @inheritDoc */ override log(namespace: LoggerNamespace, message: string, context?: LogContext): void { - if (!this.isEnabled(namespace)) { + if (!this.isEnabled(namespace, context)) { return; } @@ -25,7 +25,7 @@ export class SimpleLogger extends DefaultLogger { * @inheritDoc */ override logQuery(context: { query: string } & LogContext): void { - if (!this.isEnabled('query')) { + if (!this.isEnabled('query', context)) { return; } diff --git a/packages/knex/src/AbstractSqlConnection.ts b/packages/knex/src/AbstractSqlConnection.ts index c2064dc3a09f..ffc64632dc8a 100644 --- a/packages/knex/src/AbstractSqlConnection.ts +++ b/packages/knex/src/AbstractSqlConnection.ts @@ -12,7 +12,8 @@ import { type QueryResult, type Transaction, type TransactionEventBroadcaster, - type LoggerContext, + type LogContext, + type LoggingOptions, } from '@mikro-orm/core'; import type { AbstractSqlPlatform } from './AbstractSqlPlatform'; import { MonkeyPatchable } from './MonkeyPatchable'; @@ -125,7 +126,7 @@ export abstract class AbstractSqlConnection extends Connection { } } - async execute | EntityData[] = EntityData[]>(queryOrKnex: string | Knex.QueryBuilder | Knex.Raw, params: unknown[] = [], method: 'all' | 'get' | 'run' = 'all', ctx?: Transaction, loggerContext?: LoggerContext): Promise { + async execute | EntityData[] = EntityData[]>(queryOrKnex: string | Knex.QueryBuilder | Knex.Raw, params: unknown[] = [], method: 'all' | 'get' | 'run' = 'all', ctx?: Transaction, logging?: LoggingOptions): Promise { await this.ensureConnection(); if (Utils.isObject(queryOrKnex)) { @@ -136,7 +137,7 @@ export abstract class AbstractSqlConnection extends Connection { } const formatted = this.platform.formatQuery(queryOrKnex, params); - const sql = this.getSql(queryOrKnex, formatted); + const sql = this.getSql(queryOrKnex, formatted, logging); return this.executeQuery(sql, async () => { const query = this.getKnex().raw(formatted); @@ -146,7 +147,7 @@ export abstract class AbstractSqlConnection extends Connection { const res = await query; return this.transformRawResult(res, method); - }, { query: queryOrKnex, params, ...loggerContext }); + }, { query: queryOrKnex, params, ...logging }); } /** @@ -203,14 +204,14 @@ export abstract class AbstractSqlConnection extends Connection { return config; } - private getSql(query: string, formatted: string): string { + private getSql(query: string, formatted: string, context?: LogContext): string { const logger = this.config.getLogger(); - if (!logger.isEnabled('query')) { + if (!logger.isEnabled('query', context)) { return query; } - if (logger.isEnabled('query-params')) { + if (logger.isEnabled('query-params', context)) { return formatted; } diff --git a/packages/knex/src/AbstractSqlDriver.ts b/packages/knex/src/AbstractSqlDriver.ts index 37f9828b496c..54e44a71c39e 100644 --- a/packages/knex/src/AbstractSqlDriver.ts +++ b/packages/knex/src/AbstractSqlDriver.ts @@ -31,7 +31,7 @@ import { type IDatabaseDriver, LoadStrategy, type LockOptions, - type LoggerContext, + type LoggingOptions, type NativeInsertUpdateManyOptions, type NativeInsertUpdateOptions, type PopulateOptions, @@ -88,7 +88,7 @@ export abstract class AbstractSqlDriver[], options.fields); const joinedProps = this.joinedProps(meta, populate); - const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.loggerContext); + const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging); const fields = this.buildFields(meta, populate, joinedProps, qb, options.fields as unknown as Field[]); const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(entityName, qb, meta, joinedProps); const orderBy = [...Utils.asArray(options.orderBy), ...joinedPropsOrderBy]; @@ -902,7 +902,7 @@ export abstract class AbstractSqlDriver(entityName: EntityName | QueryBuilder, ctx?: Transaction, preferredConnectionType?: ConnectionType, convertCustomTypes?: boolean, loggerContext?: LoggerContext): QueryBuilder { + createQueryBuilder(entityName: EntityName | QueryBuilder, ctx?: Transaction, preferredConnectionType?: ConnectionType, convertCustomTypes?: boolean, logging?: LoggingOptions): QueryBuilder { const connectionType = this.resolveConnectionType({ ctx, connectionType: preferredConnectionType }); const qb = new QueryBuilder( entityName, @@ -912,7 +912,7 @@ export abstract class AbstractSqlDriver { alias?: string, private connectionType?: ConnectionType, private readonly em?: SqlEntityManager, - private readonly loggerContext?: LoggerContext) { + private readonly logging?: LoggingOptions) { if (alias) { this.aliasCounter++; this._explicitAlias = true; @@ -649,7 +649,7 @@ export class QueryBuilder { } const type = this.connectionType || (method === 'run' ? 'write' : 'read'); - const res = await this.driver.getConnection(type).execute(query.sql, query.bindings as any[], method, this.context, this.loggerContext); + const res = await this.driver.getConnection(type).execute(query.sql, query.bindings as any[], method, this.context, this.logging); const meta = this.mainAlias.metadata; if (!mapResults || !meta) { diff --git a/packages/mongodb/src/MongoConnection.ts b/packages/mongodb/src/MongoConnection.ts index 5e48af76d85d..4cf71c5e4f27 100644 --- a/packages/mongodb/src/MongoConnection.ts +++ b/packages/mongodb/src/MongoConnection.ts @@ -38,7 +38,8 @@ import { type TransactionEventBroadcaster, type UpsertOptions, type UpsertManyOptions, - type LoggerContext, + type LoggingOptions, + type LogContext, } from '@mikro-orm/core'; export class MongoConnection extends Connection { @@ -150,7 +151,7 @@ export class MongoConnection extends Connection { throw new Error(`${this.constructor.name} does not support generic execute method`); } - async find(collection: string, where: FilterQuery, orderBy?: QueryOrderMap | QueryOrderMap[], limit?: number, offset?: number, fields?: string[], ctx?: Transaction, loggerContext?: LoggerContext): Promise[]> { + async find(collection: string, where: FilterQuery, orderBy?: QueryOrderMap | QueryOrderMap[], limit?: number, offset?: number, fields?: string[], ctx?: Transaction, logging?: LoggingOptions): Promise[]> { await this.ensureConnection(); collection = this.getCollectionName(collection); const options: Dictionary = ctx ? { session: ctx } : {}; @@ -190,7 +191,7 @@ export class MongoConnection extends Connection { const now = Date.now(); const res = await resultSet.toArray(); - this.logQuery(`${query}.toArray();`, { took: Date.now() - now, results: res.length, ...loggerContext }); + this.logQuery(`${query}.toArray();`, { took: Date.now() - now, results: res.length, ...logging }); return res as EntityData[]; } @@ -215,7 +216,7 @@ export class MongoConnection extends Connection { return this.runQuery('deleteMany', collection, undefined, where, ctx); } - async aggregate(collection: string, pipeline: any[], ctx?: Transaction, loggerContext?: LoggerContext): Promise { + async aggregate(collection: string, pipeline: any[], ctx?: Transaction, logging?: LoggingOptions): Promise { await this.ensureConnection(); collection = this.getCollectionName(collection); /* istanbul ignore next */ @@ -223,7 +224,7 @@ export class MongoConnection extends Connection { const query = `db.getCollection('${collection}').aggregate(${this.logObject(pipeline)}, ${this.logObject(options)}).toArray();`; const now = Date.now(); const res = await this.getCollection(collection).aggregate(pipeline, options).toArray(); - this.logQuery(query, { took: Date.now() - now, results: res.length, ...loggerContext }); + this.logQuery(query, { took: Date.now() - now, results: res.length, ...logging }); return res; } @@ -280,7 +281,7 @@ export class MongoConnection extends Connection { await eventBroadcaster?.dispatchEvent(EventType.afterTransactionRollback, ctx); } - private async runQuery | number = QueryResult>(method: 'insertOne' | 'insertMany' | 'updateMany' | 'bulkUpdateMany' | 'deleteMany' | 'countDocuments', collection: string, data?: Partial | Partial[], where?: FilterQuery | FilterQuery[], ctx?: Transaction, upsert?: boolean, upsertOptions?: UpsertOptions, loggerContext?: LoggerContext): Promise { + private async runQuery | number = QueryResult>(method: 'insertOne' | 'insertMany' | 'updateMany' | 'bulkUpdateMany' | 'deleteMany' | 'countDocuments', collection: string, data?: Partial | Partial[], where?: FilterQuery | FilterQuery[], ctx?: Transaction, upsert?: boolean, upsertOptions?: UpsertOptions, logging?: LoggingOptions): Promise { await this.ensureConnection(); collection = this.getCollectionName(collection); const logger = this.config.getLogger(); @@ -342,7 +343,7 @@ export class MongoConnection extends Connection { break; } - this.logQuery(query!, { took: Date.now() - now, ...loggerContext }); + this.logQuery(query!, { took: Date.now() - now, ...logging }); if (method === 'countDocuments') { return res! as unknown as U; diff --git a/tests/MetadataValidator.test.ts b/tests/MetadataValidator.test.ts index 3a7d27b4e5ce..9acfc6326c87 100644 --- a/tests/MetadataValidator.test.ts +++ b/tests/MetadataValidator.test.ts @@ -141,9 +141,9 @@ describe('MetadataValidator', () => { test('validates duplicities in tableName', async () => { const properties: Dictionary = { - id: { reference: 'scalar', primary: true, name: 'id', type: 'number' }, - name: { reference: 'scalar', name: 'name', type: 'string' }, - age: { reference: 'scalar', name: 'age', type: 'string' }, + id: { kind: 'scalar', primary: true, name: 'id', type: 'number' }, + name: { kind: 'scalar', name: 'name', type: 'string' }, + age: { kind: 'scalar', name: 'age', type: 'string' }, }; const schema1 = EntitySchema.fromMetadata({ name: 'Foo1', @@ -170,9 +170,9 @@ describe('MetadataValidator', () => { test('allows duplicate classNames when "checkDuplicateEntities" is "false"', async () => { // Arrange const properties: Dictionary = { - id: { reference: 'scalar', primary: true, name: 'id', type: 'number' }, - name: { reference: 'scalar', name: 'name', type: 'string' }, - age: { reference: 'scalar', name: 'age', type: 'string' }, + id: { kind: 'scalar', primary: true, name: 'id', type: 'number' }, + name: { kind: 'scalar', name: 'name', type: 'string' }, + age: { kind: 'scalar', name: 'age', type: 'string' }, }; const schema1 = EntitySchema.fromMetadata({ name: 'Foo1', @@ -195,9 +195,9 @@ describe('MetadataValidator', () => { test('allows duplicate tableNames when "checkDuplicateTableNames" is true but "checkDuplicateEntities" is false', async () => { // Arrange const properties: Dictionary = { - id: { reference: 'scalar', primary: true, name: 'id', type: 'number' }, - name: { reference: 'scalar', name: 'name', type: 'string' }, - age: { reference: 'scalar', name: 'age', type: 'string' }, + id: { kind: 'scalar', primary: true, name: 'id', type: 'number' }, + name: { kind: 'scalar', name: 'name', type: 'string' }, + age: { kind: 'scalar', name: 'age', type: 'string' }, }; const schema1 = EntitySchema.fromMetadata({ name: 'Foo1', @@ -220,9 +220,9 @@ describe('MetadataValidator', () => { test('throws an error when duplicate entity is not provided', async () => { // Arrange const properties: Dictionary = { - id: { reference: 'scalar', primary: true, name: 'id', type: 'number' }, - name: { reference: 'scalar', name: 'name', type: 'string' }, - age: { reference: 'scalar', name: 'age', type: 'string' }, + id: { kind: 'scalar', primary: true, name: 'id', type: 'number' }, + name: { kind: 'scalar', name: 'name', type: 'string' }, + age: { kind: 'scalar', name: 'age', type: 'string' }, }; const schema1 = EntitySchema.fromMetadata({ name: 'Foo1', diff --git a/tests/Logger.test.ts b/tests/features/logging/Logger.test.ts similarity index 74% rename from tests/Logger.test.ts rename to tests/features/logging/Logger.test.ts index ed3f7c21f9bf..cb2e4e44b53d 100644 --- a/tests/Logger.test.ts +++ b/tests/features/logging/Logger.test.ts @@ -1,4 +1,4 @@ -import { DefaultLogger, SimpleLogger, colors } from '@mikro-orm/core'; +import { DefaultLogger, LogContext, SimpleLogger, colors } from '@mikro-orm/core'; // Allow for testing colored output and prevent colors from causing match failures (invis. chars) const redColorFormatterSpy = jest.spyOn(colors, 'red').mockImplementation(text => text); @@ -86,6 +86,46 @@ describe('Logger', () => { expect(yellowColorFormatterSpy).not.toBeCalled(); expect(redColorFormatterSpy).not.toBeCalled(); }); + + test('should respect the enabled context property', () => { + const logger = new DefaultLogger({ writer: mockWriter, debugMode: true }); + const namespace = 'query'; + const message = ''; + + logger.log(namespace, message, { level: 'error', enabled: true }); + expect(mockWriter).toBeCalledTimes(1); + jest.clearAllMocks(); + + logger.log(namespace, message, { level: 'error', enabled: undefined }); + expect(mockWriter).toBeCalledTimes(1); + jest.clearAllMocks(); + + logger.log(namespace, message, { level: 'error', enabled: false }); + expect(mockWriter).not.toBeCalled(); + }); + + test('should respect the debugMode context property', () => { + const logger = new DefaultLogger({ writer: mockWriter, debugMode: true }); + const message = ''; + + let options: LogContext = { debugMode: ['query'] }; + logger.log('query', message, options); + logger.log('discovery', message, options); + expect(mockWriter).toBeCalledTimes(1); + jest.clearAllMocks(); + + options = { debugMode: ['query', 'info'] }; + logger.log('query', message, options); + logger.log('info', message, options); + expect(mockWriter).toBeCalledTimes(2); + jest.clearAllMocks(); + + options = { debugMode: ['discovery', 'info'] }; + logger.log('query', message, options); + logger.log('query-params', message, options); + expect(mockWriter).not.toBeCalled(); + jest.clearAllMocks(); + }); }); describe('SimpleLogger', () => { @@ -107,5 +147,6 @@ describe('Logger', () => { expect(mockWriter).toBeCalledWith(`[${namespace}] (${label}) ${message}`); }); }); - }); + + diff --git a/tests/features/logging/logging.test.ts b/tests/features/logging/logging.test.ts new file mode 100644 index 000000000000..b8330151ceec --- /dev/null +++ b/tests/features/logging/logging.test.ts @@ -0,0 +1,73 @@ +import { Entity, LoggerNamespace, MikroORM, PrimaryKey, Property } from '@mikro-orm/core'; +import { SqliteDriver } from '@mikro-orm/sqlite'; +import { mockLogger } from '../../helpers'; + +@Entity() +export class Example { + + @PrimaryKey() + id!: number; + + @Property({ nullable: true }) + title?: string; + +} + +describe('logging', () => { + + let orm: MikroORM; + let mockedLogger: jest.Func; + const setDebug = (debug: LoggerNamespace[] = ['query', 'query-params']) => { + mockedLogger = mockLogger(orm, debug); + }; + + beforeAll(async () => { + orm = await MikroORM.init({ + entities: [Example], + dbName: ':memory:', + driver: SqliteDriver, + }); + setDebug(); + + await orm.schema.createSchema(); + + const example = new Example(); + await orm.em.persistAndFlush(example); + + }); + + afterAll(async () => { + await orm.close(true); + }); + + beforeEach(() => { + jest.clearAllMocks(); + setDebug(); + }); + + it(`logs on query - baseline`, async () => { + const ex = await orm.em.fork().findOneOrFail(Example, { id: 1 }); + expect(mockedLogger).toBeCalledTimes(1); + }); + + it(`overrides the default namespace`, async () => { + setDebug(['discovery']); + const em = orm.em.fork(); + + const example = await em.findOneOrFail(Example, { id: 1 }, { logging: { debugMode: ['query'] } }); + example.title = 'An update'; + await em.persistAndFlush(example); + + expect(mockedLogger).toBeCalledTimes(1); + }); + + it(`overrides the default debug config via the enabled flag`, async () => { + await orm.em.fork().findOneOrFail(Example, { id: 1 }, { logging: { enabled: false } }); + expect(mockedLogger).not.toBeCalled(); + + setDebug([]); + await orm.em.fork().findOneOrFail(Example, { id: 1 }, { logging: { enabled: true } }); + expect(mockedLogger).toBeCalledTimes(1); + }); + +});