{"version":3,"sources":["../src/gcpLogger.ts"],"sourcesContent":["/**\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggingWinston } from '@google-cloud/logging-winston';\nimport { getCurrentEnv } from 'genkit';\nimport { logger } from 'genkit/logging';\nimport type { Writable } from 'stream';\nimport type { GcpTelemetryConfig } from './types.js';\nimport { loggingDenied, loggingDeniedHelpText } from './utils.js';\n\n/**\n * Additional streams for writing log data to. Useful for unit testing.\n */\nlet additionalStream: Writable;\nlet useJsonFormatOverride = false;\n\n/**\n * Provides a logger for exporting Genkit debug logs to GCP Cloud\n * logs.\n */\nexport class GcpLogger {\n  constructor(private readonly config: GcpTelemetryConfig) {}\n\n  async getLogger(env: string) {\n    // Dynamically importing winston here more strictly controls\n    // the import order relative to registering instrumentation\n    // with OpenTelemetry. Incorrect import order will trigger\n    // an internal OT warning and will result in logs not being\n    // associated with correct spans/traces.\n    const winston = await import('winston');\n\n    const format =\n      useJsonFormatOverride || this.shouldExport(env)\n        ? { format: winston.format.json() }\n        : {\n            format: winston.format.printf((info): string => {\n              return `[${info.level}] ${info.message}`;\n            }),\n          };\n\n    const transports: any[] = [];\n    transports.push(\n      this.shouldExport(env)\n        ? new LoggingWinston({\n            labels: { module: 'genkit' },\n            prefix: 'genkit',\n            logName: 'genkit_log',\n            projectId: this.config.projectId,\n            credentials: this.config.credentials,\n            autoRetry: true,\n            defaultCallback: await this.getErrorHandler(),\n          })\n        : new winston.transports.Console()\n    );\n    if (additionalStream) {\n      transports.push(\n        new winston.transports.Stream({ stream: additionalStream })\n      );\n    }\n    return winston.createLogger({\n      transports: transports,\n      ...format,\n      exceptionHandlers: [new winston.transports.Console()],\n    });\n  }\n\n  private async getErrorHandler(): Promise<(err: Error | null) => void> {\n    // only log the first time\n    let instructionsLogged = false;\n    const helpInstructions = await loggingDeniedHelpText();\n\n    return async (err: Error | null) => {\n      // Use the defaultLogger so that logs don't get swallowed by\n      // the open telemetry exporter\n      const defaultLogger = logger.defaultLogger;\n      if (err && loggingDenied(err)) {\n        if (!instructionsLogged) {\n          instructionsLogged = true;\n          defaultLogger.error(\n            `Unable to send logs to Google Cloud: ${err.message}\\n\\n${helpInstructions}\\n`\n          );\n        }\n      } else if (err) {\n        defaultLogger.error(`Unable to send logs to Google Cloud: ${err}`);\n      }\n\n      if (err) {\n        // Assume the logger is compromised, and we need a new one\n        // Reinitialize the genkit logger with a new instance with the same config\n        logger.init(\n          await new GcpLogger(this.config).getLogger(getCurrentEnv())\n        );\n        defaultLogger.info('Initialized a new GcpLogger.');\n      }\n    };\n  }\n\n  private shouldExport(env?: string) {\n    return this.config.export;\n  }\n}\n\n/** @hidden */\nexport function __addTransportStreamForTesting(stream: Writable) {\n  additionalStream = stream;\n}\n\n/** @hidden */\nexport function __useJsonFormatForTesting() {\n  useJsonFormatOverride = true;\n}\n"],"mappings":"AAgBA,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,cAAc;AAGvB,SAAS,eAAe,6BAA6B;AAKrD,IAAI;AACJ,IAAI,wBAAwB;AAMrB,MAAM,UAAU;AAAA,EACrB,YAA6B,QAA4B;AAA5B;AAAA,EAA6B;AAAA,EAE1D,MAAM,UAAU,KAAa;AAM3B,UAAM,UAAU,MAAM,OAAO,SAAS;AAEtC,UAAM,SACJ,yBAAyB,KAAK,aAAa,GAAG,IAC1C,EAAE,QAAQ,QAAQ,OAAO,KAAK,EAAE,IAChC;AAAA,MACE,QAAQ,QAAQ,OAAO,OAAO,CAAC,SAAiB;AAC9C,eAAO,IAAI,KAAK,KAAK,KAAK,KAAK,OAAO;AAAA,MACxC,CAAC;AAAA,IACH;AAEN,UAAM,aAAoB,CAAC;AAC3B,eAAW;AAAA,MACT,KAAK,aAAa,GAAG,IACjB,IAAI,eAAe;AAAA,QACjB,QAAQ,EAAE,QAAQ,SAAS;AAAA,QAC3B,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,WAAW,KAAK,OAAO;AAAA,QACvB,aAAa,KAAK,OAAO;AAAA,QACzB,WAAW;AAAA,QACX,iBAAiB,MAAM,KAAK,gBAAgB;AAAA,MAC9C,CAAC,IACD,IAAI,QAAQ,WAAW,QAAQ;AAAA,IACrC;AACA,QAAI,kBAAkB;AACpB,iBAAW;AAAA,QACT,IAAI,QAAQ,WAAW,OAAO,EAAE,QAAQ,iBAAiB,CAAC;AAAA,MAC5D;AAAA,IACF;AACA,WAAO,QAAQ,aAAa;AAAA,MAC1B;AAAA,MACA,GAAG;AAAA,MACH,mBAAmB,CAAC,IAAI,QAAQ,WAAW,QAAQ,CAAC;AAAA,IACtD,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,kBAAwD;AAEpE,QAAI,qBAAqB;AACzB,UAAM,mBAAmB,MAAM,sBAAsB;AAErD,WAAO,OAAO,QAAsB;AAGlC,YAAM,gBAAgB,OAAO;AAC7B,UAAI,OAAO,cAAc,GAAG,GAAG;AAC7B,YAAI,CAAC,oBAAoB;AACvB,+BAAqB;AACrB,wBAAc;AAAA,YACZ,wCAAwC,IAAI,OAAO;AAAA;AAAA,EAAO,gBAAgB;AAAA;AAAA,UAC5E;AAAA,QACF;AAAA,MACF,WAAW,KAAK;AACd,sBAAc,MAAM,wCAAwC,GAAG,EAAE;AAAA,MACnE;AAEA,UAAI,KAAK;AAGP,eAAO;AAAA,UACL,MAAM,IAAI,UAAU,KAAK,MAAM,EAAE,UAAU,cAAc,CAAC;AAAA,QAC5D;AACA,sBAAc,KAAK,8BAA8B;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,aAAa,KAAc;AACjC,WAAO,KAAK,OAAO;AAAA,EACrB;AACF;AAGO,SAAS,+BAA+B,QAAkB;AAC/D,qBAAmB;AACrB;AAGO,SAAS,4BAA4B;AAC1C,0BAAwB;AAC1B;","names":[]}