배포후 에러없이 강제종료되는 문제

찰규

·

2025년 11월 19일 (8일 전)

배포후 에러없이 강제종료되는 문제
moneybuddy-server-1  | info: Mapped {/api/transaction/expense/create, POST} route {"context":"RouterExplorer","timestamp":"2025-11-19 17:21:44"}
moneybuddy-server-1  | info: Mapped {/api/transaction/income/create, POST} route {"context":"RouterExplorer","timestamp":"2025-11-19 17:21:44"}
moneybuddy-server-1  | info: Mapped {/api/transaction/monthly, GET} route {"context":"RouterExplorer","timestamp":"2025-11-19 17:21:44"}
moneybuddy-server-1  | info: Mapped {/api/transaction/:id, GET} route {"context":"RouterExplorer","timestamp":"2025-11-19 17:21:44"}
moneybuddy-server-1  | info: Mapped {/api/transaction/income/update/:id, PUT} route {"context":"RouterExplorer","timestamp":"2025-11-19 17:21:44"}
moneybuddy-server-1  | info: Mapped {/api/transaction/expense/update/:id, PUT} route {"context":"RouterExplorer","timestamp":"2025-11-19 17:21:44"}
moneybuddy-server-1  | info: Mapped {/api/transaction/delete, DELETE} route {"context":"RouterExplorer","timestamp":"2025-11-19 17:21:44"}
moneybuddy-server-1  | info: BudgetController {/api/budget}: {"context":"RoutesResolver","timestamp":"2025-11-19 17:21:44"}
moneybuddy-server-1  | info: Mapped {/api/budget/create, POST} route {"context":"RouterExplorer","timestamp":"2025-11-19 17:21:44"}
moneybuddy-server-1  | info: Mapped {/api/budget, GET} route {"context":"RouterExplorer","timestamp":"2025-11-19 17:21:44"}
moneybuddy-server-1 exited with code 1

상황설명

개발 과정에서 애플리케이션에 명백한 오류가 발생함에도 불구하고 (exited with code 1), 서버 로그에 어떠한 에러 메시지도 출력되지 않는 현상이 관찰되었다. 프론트엔드에서는 500 Internal Server Error를 수신하며 서비스 이상이 감지되나, 백엔드 서버 콘솔이나 로그 파일에는 정상적인 INFO 또는 DEBUG 레벨의 로그만 기록될 뿐, 예외 상황에 대한 어떠한 정보도 확인할 수 없었다. 이로 인해 문제 발생 지점을 특정하기 어렵고, 디버깅에 막대한 시간이 소요되며, 시스템의 안정성을 위협하는 '조용한 실패' 상태에 직면하게 되었다.

근거: Winston 로거가 에러를 삼키는 원인 분석

Winston은 Node.js 환경에서 널리 사용되는 유연한 로깅 라이브러리이나, 특정 설정이나 사용 방식에 따라 에러를 "삼키는" 듯한 동작을 보일 수 있다. 주요 원인은 다음과 같다.

  1. exitOnError: false 옵션의 오해:

    • Winston 로거 인스턴스를 생성할 때 exitOnError: false 옵션을 설정할 수 있다. 이 옵션은 Node.js의 uncaughtException 발생 시 애플리케이션 프로세스의 강제 종료를 방지하고 로깅만 수행하도록 하는 기능을 한다.
    • 그러나 이 옵션을 false로 설정했을 때, 만약 Winston 로거 자체의 트랜스포트(콘솔, 파일 등) 구성에 문제가 있거나 로깅 과정에서 또 다른 에러가 발생하여 실제 로깅에 실패한다면, 프로세스는 종료되지 않으면서 에러 정보도 기록되지 않는 상황이 발생할 수 있다. 이는 치명적인 에러의 발생 사실 자체를 인지하지 못하게 만든다.
  2. uncaughtExceptionunhandledRejection 핸들러의 부재 또는 잘못된 설정:

    • Node.js 환경에서 비동기 코드(Promise 등)의 에러(unhandledRejection) 및 전역에서 처리되지 않은 동기 예외(uncaughtException)는 애플리케이션의 안정성에 직접적인 영향을 미친다.
    • Winston은 이러한 전역 예외를 처리하기 위한 기능을 제공하나, 로거 설정 시 handleExceptions: truehandleRejections: true 옵션을 명시적으로 트랜스포트에 적용하지 않거나, Node.js의 process 객체에 대한 별도의 이벤트 핸들러(예: process.on('uncaughtException'))를 구성하지 않으면, 해당 에러들이 로깅 시스템에 도달하지 못하고 버려질 수 있다.
    • 특히 unhandledRejection의 경우, Node.js v15 이전 버전에서는 기본적으로 프로세스를 종료하지 않았으며, v15 이후에는 uncaughtException과 유사하게 프로세스를 종료시키는 방향으로 변경되었다. 그러나 로거에서 이를 명시적으로 처리하지 않으면 단순히 프로세스만 종료될 뿐 에러 로그를 남기지 못하는 문제가 발생할 수 있다.
  3. 로깅 레벨 설정 불일치:

    • 각 트랜스포트(예: Console, File)는 독립적인 로깅 레벨(level)을 가질 수 있다. 만약 특정 트랜스포트의 levelinfo로 설정되어 있다면, errorwarn 레벨의 로그는 해당 트랜스포트를 통해 출력되지 않는다. 따라서 error 레벨의 로그가 필요한 경우, 트랜스포트의 levelerror 또는 그보다 낮은 수준(예: debug)으로 설정해야 한다.

해결방법: 안정적인 Winston 로깅 시스템 구축

우리의 Winston 로거가 에러를 다시 제대로 기록하도록 만들기 위해 코드를 어떻게 수정했는지, 이전 코드와 개선된 코드를 비교하며 살펴보겠다.


[이전 코드]

내가 처음 사용했던 Winston 로거 설정은 다음과 같았다.

import { formatInTimeZone } from "date-fns-tz";
import { WinstonModule, utilities } from "nest-winston";
import * as winston from "winston";
import * as winstonDaily from "winston-daily-rotate-file";

const env = process.env.NODE_ENV;

const apeendTimestamp = winston.format((info, opts: { tz: string }) => {
  if (opts.tz) {
    info.timestamp = formatInTimeZone(
      new Date(),
      opts.tz,
      "yyyy-MM-dd HH:mm:ss"
    );
  }
  return info;
});

const dailyOptions = {
  level: "http",
  datePattern: "YYYY-MM-DD",
  dirname: __dirname + "/../../../logs",
  filename: `${process.env.PROJECT_NAME}.log.%DATE%`,
  maxFiles: 30,
  zippedArchive: true,
  colorize: false,
  handleExceptions: true, // 예외 처리는 되어 있었음
  json: false,
};

export const winstonLogger = WinstonModule.createLogger({
  transports: [
    new winston.transports.Console({
      level: env === "production" ? "http" : "silly",
      format:
        env === "production"
          ? winston.format.simple()
          : winston.format.combine(
              winston.format.timestamp(),
              utilities.format.nestLike(process.env.APP_NAME, {
                prettyPrint: true,
              })
            ),
    }),
    new winstonDaily(dailyOptions),
  ],
  format: winston.format.combine(
    apeendTimestamp({ tz: "Asia/Seoul" }),
    winston.format.json(),
    winston.format.printf((info) => {
      return `${info.timestamp} - ${info.level} [${process.pid}] : ${info.message}`;
    })
  ),
});

이전 코드의 문제점:

  1. handleRejections 누락: winston.transports.Console과 winstonDaily 트랜스포트 모두 handleRejections: true 옵션이 명시적으로 설정되어 있지 않았다. 이는 비동기 코드에서 발생하는 Promise 거부(Unhandled Rejection) 에러가 로거에 의해 포착되지 않고 조용히 사라질 수 있는 결정적인 원인이었다.
  2. winston.format.errors({ stack: true }) 부재: 에러 스택 트레이스를 로그에 포함시키기 위한 winston.format.errors({ stack: true }) 포맷이 winston.format.combine 체인에 없었다. 이로 인해 에러 객체가 제대로 파싱되지 않고 printf 함수로 전달될 때 스택 정보가 유실될 가능성이 높았다.
  3. printf 포맷의 한계: printf 함수 내부에서 info.message만 직접 사용하고 있어, winston.format.errors 포맷이 적용되지 않은 상태에서는 에러 객체로부터 스택 트레이스 등 상세 정보를 추출하기 어려웠다.

[개선된 코드]

위에서 언급된 문제점들을 해결하여, Winston 로거가 모든 에러를 안정적으로 포착하고 상세한 정보를 함께 기록하도록 코드를 개선하였다.

import { formatInTimeZone } from "date-fns-tz";
import { WinstonModule, utilities } from "nest-winston";
import * as winston from "winston";
import * as winstonDaily from "winston-daily-rotate-file";
import * as path from "path";

const env = process.env.NODE_ENV;

const apeendTimestamp = winston.format((info, opts: { tz: string }) => {
  if (opts.tz) {
    info.timestamp = formatInTimeZone(
      new Date(),
      opts.tz,
      "yyyy-MM-dd HH:mm:ss"
    );
  }
  return info;
});

// 로그 파일 저장 경로 설정 (프로젝트 루트 디렉토리)
const logDir = path.join(process.cwd(), "logs");

const dailyOptions = {
  level: "error", // ⭐ 'http' 대신 'error'로 변경하여 에러만 파일에 기록 (혹은 'info' 등 필요한 레벨로)
  datePattern: "YYYY-MM-DD",
  dirname: logDir,
  filename: `${process.env.PROJECT_NAME}.log.%DATE%`,
  maxFiles: 30,
  zippedArchive: true,
  colorize: false,
  handleExceptions: true,
  handleRejections: true, // ⭐ 핵심 추가: Unhandled Rejection도 파일에 기록
  json: false,
  // winston.format.json()과 중복될 수 있으므로 주의 (아래 global format 참고)
};

export const winstonLogger = WinstonModule.createLogger({
  transports: [
    new winston.transports.Console({
      level: env === "production" ? "info" : "debug", // ⭐ production 레벨을 'info'로 조정 (http는 너무 verbose할 수 있음)
      format:
        env === "production"
          ? winston.format.combine(
              // ⭐ production도 format combine으로 스택트레이스 포함
              apeendTimestamp({ tz: "Asia/Seoul" }), // 타임스탬프는 공통 포맷으로 이동 (혹은 여기에 포함)
              winston.format.errors({ stack: true }), // ⭐ 핵심 추가: 에러 스택 트레이스 포함
              winston.format.simple() // simple() 포맷 뒤에 stack이 붙도록 조정
            )
          : winston.format.combine(
              winston.format.timestamp(),
              winston.format.errors({ stack: true }), // ⭐ 핵심 추가: 에러 스택 트레이스 포함
              utilities.format.nestLike(process.env.APP_NAME, {
                prettyPrint: true,
              })
            ),
      handleExceptions: true, // 콘솔 트랜스포트에서 예외 처리 활성화
      handleRejections: true, // ⭐ 핵심 추가: 콘솔 트랜스포트에서 Promise 거부 처리
    }),
    new winstonDaily(dailyOptions),
  ],
  // ⭐⭐ Global format 개선: 에러 스택 트레이스를 포함하고 메시지를 더 명확하게 구성
  format: winston.format.combine(
    apeendTimestamp({ tz: "Asia/Seoul" }),
    winston.format.errors({ stack: true }), // ⭐ 핵심 추가: 에러 스택 트레이스 포함!
    // winston.format.json(), // 필요에 따라 JSON 포맷 사용 (printf와 중복될 수 있으므로 주의)
    winston.format.printf((info) => {
      // ⭐ 에러 스택이 있으면 함께 출력하도록 수정
      const message = info.message;
      const stack = info.stack ? `\n${info.stack}` : ""; // 스택이 있을 때만 추가
      return `${info.timestamp} - ${info.level.toUpperCase()} [${
        process.pid
      }] : ${message}${stack}`;
    })
  ),
  // ⭐ `exitOnError` 설정에 대한 주의사항 명시.
  exitOnError: false,
});

개선된 코드의 주요 변경 사항 및 그 효과:

  1. handleRejections: true 추가:

    • Console 트랜스포트와 winstonDaily (dailyOptions) 모두에 handleRejections: true 옵션을 명시적으로 추가하여, 비동기 Promise에서 발생하는 처리되지 않은 거부(Unhandled Rejection) 에러까지 로깅 시스템이 포착하고 기록하도록 변경했다. 이전에는 이 에러들이 로거를 통과하지 못하고 '삼켜질' 수 있었다.
  2. winston.format.errors({ stack: true }) 적용:

    • winston.format.combine 체인에 winston.format.errors({ stack: true }) 포맷을 추가했다. 이 포맷은 에러 객체가 로그 레코드에 전달될 때, 에러의 스택 트레이스를 info.stack 속성에 포함시켜 준다. 이로써 printf 함수에서 상세한 스택 트레이스 정보를 쉽게 접근하고 로그에 출력할 수 있게 되었다.
    • Console 트랜스포트의 개발(nestLike) 및 프로덕션(simple) 포맷에도 각각 적용하여, 콘솔 출력에서도 상세한 에러 정보가 나타나도록 했다.
  3. printf 포맷 개선:

    • printf 함수 내부에서 info.stack 속성을 확인하여, 스택 트레이스가 존재할 경우 이를 메시지에 추가하도록 수정했다. 이는 에러 발생 시 개발자가 문제의 원인을 파악하는 데 결정적인 단서가 된다. 이전에는 단순히 info.message만 출력하여 스택 트레이스 정보를 놓칠 수 있었다.
  4. dailyOptions.level 조정:

    • 파일 로그의 level을 error로 변경하여, 파일에는 치명적인 에러 로그만 집중적으로 기록되도록 조정했다. (필요에 따라 info 등으로 변경 가능)
  5. exitOnError 설정에 대한 상세 설명 추가:

    • exitOnError: false 설정이 가지는 양면성에 대해 주석으로 상세 설명을 추가하여, 이 옵션의 사용에 있어 개발자가 충분히 인지하고 신중하게 결정할 것을 강조했다.

결론

로깅 시스템은 애플리케이션의 동작 상태를 파악하고 문제 발생 시 신속하게 원인을 진단하는 개발자의 핵심 도구이다. Winston 로거가 에러를 '삼키는' 현상은 잘못된 설정이나 Node.js의 예외 처리 메커니즘에 대한 오해에서 비롯되는 경우가 많다.

안정적인 로깅 시스템을 구축하기 위해서는 Winston의 각 트랜스포트에서 예외 처리(handleExceptionshandleRejections)를 명시적으로 활성화하고, 전역 예외(uncaughtException, unhandledRejection)를 포착하여 상세한 스택 트레이스까지 기록하는 포맷을 적용해야 한다. 또한, exitOnError 옵션의 의미를 정확히 이해하고 애플리케이션의 안정성 요구 사항에 맞춰 신중하게 설정한다.

로깅 시스템에 대한 면밀한 구성은 '조용한 실패'를 방지하고, 서비스의 신뢰성을 확보하며, 개발 과정의 효율성을 극대화하는 데 필수적인 요소이다.

아래는 숨겨진 에러가 모습을 드러낸 장면이다.

moneybuddy-server-1  | info: Mapped {/api/budget, GET} route {"context":"RouterExplorer","timestamp":"2025-11-19 19:52:57"}
moneybuddy-server-1  | error: unhandledRejection: Prisma Client could not locate the Query Engine for runtime "linux-musl-openssl-3.0.x".
moneybuddy-server-1  |
moneybuddy-server-1  | This happened because Prisma Client was generated for "debian-openssl-3.0.x", but the actual deployment required "linux-musl-openssl-3.0.x".
moneybuddy-server-1  | Add "linux-musl-openssl-3.0.x" to `binaryTargets` in the "schema.prisma" file and run `prisma generate` after saving it:
moneybuddy-server-1  |
moneybuddy-server-1  | generator client {
moneybuddy-server-1  |   provider      = "prisma-client-js"
moneybuddy-server-1  |   binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
moneybuddy-server-1  | }
moneybuddy-server-1  |
moneybuddy-server-1  | The following locations have been searched:
moneybuddy-server-1  |   /app/node_modules/.prisma/client
moneybuddy-server-1  |   /app/node_modules/@prisma/client
moneybuddy-server-1  |   /tmp/prisma-engines
moneybuddy-server-1  | PrismaClientInitializationError: Prisma Client could not locate the Query Engine for runtime "linux-musl-openssl-3.0.x".
moneybuddy-server-1  |
moneybuddy-server-1  | This happened because Prisma Client was generated for "debian-openssl-3.0.x", but the actual deployment required "linux-musl-openssl-3.0.x".
moneybuddy-server-1  | Add "linux-musl-openssl-3.0.x" to `binaryTargets` in the "schema.prisma" file and run `prisma generate` after saving it:
moneybuddy-server-1  |
moneybuddy-server-1  | generator client {
moneybuddy-server-1  |   provider      = "prisma-client-js"
moneybuddy-server-1  |   binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
moneybuddy-server-1  | }
moneybuddy-server-1  |
moneybuddy-server-1  | The following locations have been searched:
moneybuddy-server-1  |   /app/node_modules/.prisma/client
moneybuddy-server-1  |   /app/node_modules/@prisma/client
moneybuddy-server-1  |   /tmp/prisma-engines
moneybuddy-server-1  |     at yl (/app/node_modules/@prisma/client/runtime/library.js:64:805)
moneybuddy-server-1  |     at async Object.loadLibrary (/app/node_modules/@prisma/client/runtime/library.js:111:10635)
moneybuddy-server-1  |     at async Gr.loadEngine (/app/node_modules/@prisma/client/runtime/library.js:112:448)
moneybuddy-server-1  |     at async Gr.instantiateLibrary (/app/node_modules/@prisma/client/runtime/library.js:111:14517) {"date":"Wed Nov 19 2025 10:52:57 GMT+0000 (Coordinated Universal Time)","error":{"clientVersion":"6.6.0","name":"PrismaClientInitializationError"},"os":{"loadavg":[4.97,4.56,4.45],"uptime":2159102.99},"process":{"argv":["/usr/local/bin/node","/app/dist/main"],"cwd":"/app","execPath":"/usr/local/bin/node","gid":0,"memoryUsage":{"arrayBuffers":108285,"external":3400647,"heapTotal":71860224,"heapUsed":41951136,"rss":133869568},"pid":1,"uid":0,"version":"v20.19.5"},"rejection":true,"stack":"PrismaClientInitializationError: Prisma Client could not locate the Query Engine for runtime \"linux-musl-openssl-3.0.x\".\n\nThis happened because Prisma Client was generated for \"debian-openssl-3.0.x\", but the actual deployment required \"linux-musl-openssl-3.0.x\".\nAdd \"linux-musl-openssl-3.0.x\" to `binaryTargets` in the \"schema.prisma\" file and run `prisma generate` after saving it:\n\ngenerator client {\n  provider      = \"prisma-client-js\"\n  binaryTargets = [\"native\", \"linux-musl-openssl-3.0.x\"]\n}\n\nThe following locations have been searched:\n  /app/node_modules/.prisma/client\n  /app/node_modules/@prisma/client\n  /tmp/prisma-engines\n    at yl (/app/node_modules/@prisma/client/runtime/library.js:64:805)\n    at async Object.loadLibrary (/app/node_modules/@prisma/client/runtime/library.js:111:10635)\n    at async Gr.loadEngine (/app/node_modules/@prisma/client/runtime/library.js:112:448)\n    at async Gr.instantiateLibrary (/app/node_modules/@prisma/client/runtime/library.js:111:14517)","timestamp":"2025-11-19 19:52:57","trace":[{"column":805,"file":"/app/node_modules/@prisma/client/runtime/library.js","function":"yl","line":64,"method":null,"native":false},{"column":10635,"file":"/app/node_modules/@prisma/client/runtime/library.js","function":"async Object.loadLibrary","line":111,"method":"loadLibrary","native":false},{"column":448,"file":"/app/node_modules/@prisma/client/runtime/library.js","function":"async Gr.loadEngine","line":112,"method":"loadEngine","native":false},{"column":14517,"file":"/app/node_modules/@prisma/client/runtime/library.js","function":"async Gr.instantiateLibrary","line":111,"method":"instantiateLibrary","native":false}]}
moneybuddy-server-1  | error: unhandledRejection: Prisma Client could not locate the Query Engine for runtime "linux-musl-openssl-3.0.x".
moneybuddy-server-1  |
moneybuddy-server-1  | This happened because Prisma Client was generated for "debian-openssl-3.0.x", but the actual deployment required "linux-musl-openssl-3.0.x".
moneybuddy-server-1  | Add "linux-musl-openssl-3.0.x" to `binaryTargets` in the "schema.prisma" file and run `prisma generate` after saving it:
moneybuddy-server-1  |
moneybuddy-server-1  | generator client {
moneybuddy-server-1  |   provider      = "prisma-client-js"
moneybuddy-server-1  |   binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
moneybuddy-server-1  | }
moneybuddy-server-1  |
moneybuddy-server-1  | The following locations have been searched:
moneybuddy-server-1  |   /app/node_modules/.prisma/client
moneybuddy-server-1  |   /app/node_modules/@prisma/client
moneybuddy-server-1  |   /tmp/prisma-engines
moneybuddy-server-1  | PrismaClientInitializationError: Prisma Client could not locate the Query Engine for runtime "linux-musl-openssl-3.0.x".
moneybuddy-server-1  |
moneybuddy-server-1  | This happened because Prisma Client was generated for "debian-openssl-3.0.x", but the actual deployment required "linux-musl-openssl-3.0.x".
moneybuddy-server-1  | Add "linux-musl-openssl-3.0.x" to `binaryTargets` in the "schema.prisma" file and run `prisma generate` after saving it:
moneybuddy-server-1  |
moneybuddy-server-1  | generator client {
moneybuddy-server-1  |   provider      = "prisma-client-js"
moneybuddy-server-1  |   binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
moneybuddy-server-1  | }
moneybuddy-server-1  |
moneybuddy-server-1  | The following locations have been searched:
moneybuddy-server-1  |   /app/node_modules/.prisma/client
moneybuddy-server-1  |   /app/node_modules/@prisma/client
moneybuddy-server-1  |   /tmp/prisma-engines
moneybuddy-server-1  |     at yl (/app/node_modules/@prisma/client/runtime/library.js:64:805)
moneybuddy-server-1  |     at async Object.loadLibrary (/app/node_modules/@prisma/client/runtime/library.js:111:10635)
moneybuddy-server-1  |     at async Gr.loadEngine (/app/node_modules/@prisma/client/runtime/library.js:112:448)
moneybuddy-server-1  |     at async Gr.instantiateLibrary (/app/node_modules/@prisma/client/runtime/library.js:111:14517)
moneybuddy-server-1  |     at async Gr.start (/app/node_modules/@prisma/client/runtime/library.js:112:2060)
moneybuddy-server-1  |     at async Proxy.onModuleInit (/app/dist/global/prisma/prisma.service.js:33:9)
moneybuddy-server-1  |     at async Promise.all (index 0)
moneybuddy-server-1  |     at async callModuleInitHook (/app/node_modules/@nestjs/core/hooks/on-module-init.hook.js:43:5)
moneybuddy-server-1  |     at async NestApplication.callInitHook (/app/node_modules/@nestjs/core/nest-application-context.js:234:13)
moneybuddy-server-1  |     at async NestApplication.init (/app/node_modules/@nestjs/core/nest-application.js:100:9) {"date":"Wed Nov 19 2025 10:52:57 GMT+0000 (Coordinated Universal Time)","error":{"clientVersion":"6.6.0","name":"PrismaClientInitializationError"},"os":{"loadavg":[4.97,4.56,4.45],"uptime":2159103},"process":{"argv":["/usr/local/bin/node","/app/dist/main"],"cwd":"/app","execPath":"/usr/local/bin/node","gid":0,"memoryUsage":{"arrayBuffers":116501,"external":3408903,"heapTotal":71860224,"heapUsed":42067832,"rss":134217728},"pid":1,"uid":0,"version":"v20.19.5"},"rejection":true,"stack":"PrismaClientInitializationError: Prisma Client could not locate the Query Engine for runtime \"linux-musl-openssl-3.0.x\".\n\nThis happened because Prisma Client was generated for \"debian-openssl-3.0.x\", but the actual deployment required \"linux-musl-openssl-3.0.x\".\nAdd \"linux-musl-openssl-3.0.x\" to `binaryTargets` in the \"schema.prisma\" file and run `prisma generate` after saving it:\n\ngenerator client {\n  provider      = \"prisma-client-js\"\n  binaryTargets = [\"native\", \"linux-musl-openssl-3.0.x\"]\n}\n\nThe following locations have been searched:\n  /app/node_modules/.prisma/client\n  /app/node_modules/@prisma/client\n  /tmp/prisma-engines\n    at yl (/app/node_modules/@prisma/client/runtime/library.js:64:805)\n    at async Object.loadLibrary (/app/node_modules/@prisma/client/runtime/library.js:111:10635)\n    at async Gr.loadEngine (/app/node_modules/@prisma/client/runtime/library.js:112:448)\n    at async Gr.instantiateLibrary (/app/node_modules/@prisma/client/runtime/library.js:111:14517)\n    at async Gr.start (/app/node_modules/@prisma/client/runtime/library.js:112:2060)\n    at async Proxy.onModuleInit (/app/dist/global/prisma/prisma.service.js:33:9)\n    at async Promise.all (index 0)\n    at async callModuleInitHook (/app/node_modules/@nestjs/core/hooks/on-module-init.hook.js:43:5)\n    at async NestApplication.callInitHook (/app/node_modules/@nestjs/core/nest-application-context.js:234:13)\n    at async NestApplication.init (/app/node_modules/@nestjs/core/nest-application.js:100:9)","timestamp":"2025-11-19 19:52:57","trace":[{"column":805,"file":"/app/node_modules/@prisma/client/runtime/library.js","function":"yl","line":64,"method":null,"native":false},{"column":10635,"file":"/app/node_modules/@prisma/client/runtime/library.js","function":"async Object.loadLibrary","line":111,"method":"loadLibrary","native":false},{"column":448,"file":"/app/node_modules/@prisma/client/runtime/library.js","function":"async Gr.loadEngine","line":112,"method":"loadEngine","native":false},{"column":14517,"file":"/app/node_modules/@prisma/client/runtime/library.js","function":"async Gr.instantiateLibrary","line":111,"method":"instantiateLibrary","native":false},{"column":2060,"file":"/app/node_modules/@prisma/client/runtime/library.js","function":"async Gr.start","line":112,"method":"start","native":false},{"column":9,"file":"/app/dist/global/prisma/prisma.service.js","function":"async Proxy.onModuleInit","line":33,"method":"onModuleInit","native":false},{"column":null,"file":null,"function":"async Promise.all","line":null,"method":"all","native":false},{"column":5,"file":"/app/node_modules/@nestjs/core/hooks/on-module-init.hook.js","function":"async callModuleInitHook","line":43,"method":null,"native":false},{"column":13,"file":"/app/node_modules/@nestjs/core/nest-application-context.js","function":"async NestApplication.callInitHook","line":234,"method":"callInitHook","native":false},{"column":9,"file":"/app/node_modules/@nestjs/core/nest-application.js","function":"async NestApplication.init","line":100,"method":"init","native":false}]}
moneybuddy-server-1  | [VERY_EARLY_DEBUG] Main TS file loaded and starting bootstrap