Skip to content

Commit

Permalink
Add support for single point of error logging
Browse files Browse the repository at this point in the history
In case error logging is enabled in the client, a middleware
captures errors and uses the event emitter to notify about them. This way, errors are captured in the same way accross the different engines, by capturing errors one level above in the call stack (at the client level)

After this approach is validated, next commits will:
- Filter the kind of errors that should be logged
- Create the appropriate structure for the logging event being emitted
- Add unit tests to check the behavior with the binary engine.
  • Loading branch information
miguelff authored and jkomyno committed Dec 21, 2022
1 parent 53625c7 commit c28cff9
Show file tree
Hide file tree
Showing 5 changed files with 22 additions and 14 deletions.
14 changes: 14 additions & 0 deletions packages/client/src/runtime/getPrismaClient.ts
Expand Up @@ -20,6 +20,7 @@ import type { DataSource, GeneratorConfig } from '@prisma/generator-helper'
import { callOnce, ClientEngineType, getClientEngineType, logger, tryLoadEnvs, warnOnce } from '@prisma/internals'
import type { LoadedEnv } from '@prisma/internals/dist/utils/tryLoadEnvs'
import { AsyncResource } from 'async_hooks'
import { EventEmitter } from 'events'
import fs from 'fs'
import path from 'path'
import { RawValue, Sql } from 'sql-template-tag'
Expand Down Expand Up @@ -336,12 +337,14 @@ export function getPrismaClient(config: GetPrismaClientConfig) {
_rejectOnNotFound?: InstanceRejectOnNotFound
_dataProxy: boolean
_extensions: Extension[]
_logEmitter: EventEmitter

constructor(optionsArg?: PrismaClientOptions) {
if (optionsArg) {
validatePrismaClientOptions(optionsArg, config.datasourceNames)
}

this._logEmitter = new EventEmitter()
this._extensions = []
this._previewFeatures = config.generator?.previewFeatures ?? []
this._rejectOnNotFound = optionsArg?.rejectOnNotFound
Expand Down Expand Up @@ -447,6 +450,7 @@ export function getPrismaClient(config: GetPrismaClientConfig) {
inlineDatasources: config.inlineDatasources,
inlineSchemaHash: config.inlineSchemaHash,
tracingConfig: this._tracingConfig,
logEmitter: this._logEmitter,
}

debug('clientVersion', config.clientVersion)
Expand All @@ -470,6 +474,16 @@ export function getPrismaClient(config: GetPrismaClientConfig) {
logger.log(`${logger.tags[level] ?? ''}`, event.message || event.query)
})
}

// emit events for unhandled errors
if (typeof log === 'string' ? log === 'error' : log.level === 'error') {
this.$use((params, next) => {
return next(params).catch((e) => {
this._logEmitter.emit('error', e)
throw e
})
})
}
}
}

Expand Down
6 changes: 2 additions & 4 deletions packages/engine-core/src/binary/BinaryEngine.ts
Expand Up @@ -149,6 +149,7 @@ export class BinaryEngine extends Engine {
dirname,
activeProvider,
tracingConfig,
logEmitter,
}: EngineConfig) {
super()

Expand All @@ -162,10 +163,7 @@ export class BinaryEngine extends Engine {
this.generator = generator
this.datasources = datasources
this.tracingConfig = tracingConfig
this.logEmitter = new EventEmitter()
this.logEmitter.on('error', () => {
// to prevent unhandled error events
})
this.logEmitter = logEmitter
this.showColors = showColors ?? false
this.logLevel = logLevel
this.logQueries = logQueries ?? false
Expand Down
6 changes: 4 additions & 2 deletions packages/engine-core/src/common/Engine.ts
@@ -1,12 +1,13 @@
import type { DataSource, DMMF, GeneratorConfig } from '@prisma/generator-helper'
import type { DataSource, DMMF, EnvVlue, GeneratorConfig } from '@prisma/generator-helper'
import { EventEmitter } from 'events'

import { TracingConfig } from '../tracing/getTracingConfig'
import type { Metrics, MetricsOptionsJson, MetricsOptionsPrometheus } from './types/Metrics'
import type { QueryEngineRequestHeaders, QueryEngineResult } from './types/QueryEngine'
import type * as Transaction from './types/Transaction'

export interface FilterConstructor {
new (config: EngineConfig): Engine
new(config: EngineConfig): Engine
}

export type NullableEnvValue = {
Expand Down Expand Up @@ -97,6 +98,7 @@ export interface EngineConfig {
previewFeatures?: string[]
engineEndpoint?: string
activeProvider?: string
logEmitter: EventEmitter

/**
* The contents of the schema encoded into a string
Expand Down
4 changes: 1 addition & 3 deletions packages/engine-core/src/data-proxy/DataProxyEngine.ts
Expand Up @@ -62,9 +62,7 @@ export class DataProxyEngine extends Engine {
this.inlineDatasources = config.inlineDatasources ?? {}
this.inlineSchemaHash = config.inlineSchemaHash ?? ''
this.clientVersion = config.clientVersion ?? 'unknown'

this.logEmitter = new EventEmitter()
this.logEmitter.on('error', () => {})
this.logEmitter = config.logEmitter

const [host, apiKey] = this.extractHostAndApiKey()
this.remoteClientVersion = P.then(() => getClientVersion(this.config))
Expand Down
6 changes: 1 addition & 5 deletions packages/engine-core/src/library/LibraryEngine.ts
Expand Up @@ -101,11 +101,7 @@ export class LibraryEngine extends Engine {
this.logQueries = config.logQueries ?? false
this.logLevel = config.logLevel ?? 'error'
this.libraryLoader = loader
this.logEmitter = new EventEmitter()
this.logEmitter.on('error', (e) => {
// to prevent unhandled error events
// TODO: should we actually handle them instead of silently swallowing?
})
this.logEmitter = config.logEmitter
this.datasourceOverrides = config.datasources ? this.convertDatasources(config.datasources) : {}
if (config.enableDebugLogs) {
this.logLevel = 'debug'
Expand Down

0 comments on commit c28cff9

Please sign in to comment.