@frengki0707/google-cloud-clone 1.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/LICENSE +203 -0
  2. package/README.md +83 -0
  3. package/lib/auth.d.mts +33 -0
  4. package/lib/auth.d.ts +33 -0
  5. package/lib/auth.js +70 -0
  6. package/lib/auth.js.map +1 -0
  7. package/lib/auth.mjs +45 -0
  8. package/lib/auth.mjs.map +1 -0
  9. package/lib/gcpLogger.d.mts +25 -0
  10. package/lib/gcpLogger.d.ts +25 -0
  11. package/lib/gcpLogger.js +118 -0
  12. package/lib/gcpLogger.js.map +1 -0
  13. package/lib/gcpLogger.mjs +82 -0
  14. package/lib/gcpLogger.mjs.map +1 -0
  15. package/lib/gcpOpenTelemetry.d.mts +59 -0
  16. package/lib/gcpOpenTelemetry.d.ts +59 -0
  17. package/lib/gcpOpenTelemetry.js +374 -0
  18. package/lib/gcpOpenTelemetry.js.map +1 -0
  19. package/lib/gcpOpenTelemetry.mjs +364 -0
  20. package/lib/gcpOpenTelemetry.mjs.map +1 -0
  21. package/lib/index.d.mts +36 -0
  22. package/lib/index.d.ts +36 -0
  23. package/lib/index.js +56 -0
  24. package/lib/index.js.map +1 -0
  25. package/lib/index.mjs +29 -0
  26. package/lib/index.mjs.map +1 -0
  27. package/lib/metrics.d.mts +65 -0
  28. package/lib/metrics.d.ts +65 -0
  29. package/lib/metrics.js +91 -0
  30. package/lib/metrics.js.map +1 -0
  31. package/lib/metrics.mjs +65 -0
  32. package/lib/metrics.mjs.map +1 -0
  33. package/lib/model-armor.d.mts +59 -0
  34. package/lib/model-armor.d.ts +59 -0
  35. package/lib/model-armor.js +205 -0
  36. package/lib/model-armor.js.map +1 -0
  37. package/lib/model-armor.mjs +181 -0
  38. package/lib/model-armor.mjs.map +1 -0
  39. package/lib/telemetry/action.d.mts +27 -0
  40. package/lib/telemetry/action.d.ts +27 -0
  41. package/lib/telemetry/action.js +92 -0
  42. package/lib/telemetry/action.js.map +1 -0
  43. package/lib/telemetry/action.mjs +73 -0
  44. package/lib/telemetry/action.mjs.map +1 -0
  45. package/lib/telemetry/defaults.d.mts +30 -0
  46. package/lib/telemetry/defaults.d.ts +30 -0
  47. package/lib/telemetry/defaults.js +70 -0
  48. package/lib/telemetry/defaults.js.map +1 -0
  49. package/lib/telemetry/defaults.mjs +46 -0
  50. package/lib/telemetry/defaults.mjs.map +1 -0
  51. package/lib/telemetry/engagement.d.mts +35 -0
  52. package/lib/telemetry/engagement.d.ts +35 -0
  53. package/lib/telemetry/engagement.js +106 -0
  54. package/lib/telemetry/engagement.js.map +1 -0
  55. package/lib/telemetry/engagement.mjs +85 -0
  56. package/lib/telemetry/engagement.mjs.map +1 -0
  57. package/lib/telemetry/feature.d.mts +35 -0
  58. package/lib/telemetry/feature.d.ts +35 -0
  59. package/lib/telemetry/feature.js +142 -0
  60. package/lib/telemetry/feature.js.map +1 -0
  61. package/lib/telemetry/feature.mjs +127 -0
  62. package/lib/telemetry/feature.mjs.map +1 -0
  63. package/lib/telemetry/generate.d.mts +53 -0
  64. package/lib/telemetry/generate.d.ts +53 -0
  65. package/lib/telemetry/generate.js +326 -0
  66. package/lib/telemetry/generate.js.map +1 -0
  67. package/lib/telemetry/generate.mjs +314 -0
  68. package/lib/telemetry/generate.mjs.map +1 -0
  69. package/lib/telemetry/path.d.mts +32 -0
  70. package/lib/telemetry/path.d.ts +32 -0
  71. package/lib/telemetry/path.js +91 -0
  72. package/lib/telemetry/path.js.map +1 -0
  73. package/lib/telemetry/path.mjs +78 -0
  74. package/lib/telemetry/path.mjs.map +1 -0
  75. package/lib/types.d.mts +121 -0
  76. package/lib/types.d.ts +121 -0
  77. package/lib/types.js +17 -0
  78. package/lib/types.js.map +1 -0
  79. package/lib/types.mjs +1 -0
  80. package/lib/types.mjs.map +1 -0
  81. package/lib/utils.d.mts +57 -0
  82. package/lib/utils.d.ts +57 -0
  83. package/lib/utils.js +143 -0
  84. package/lib/utils.js.map +1 -0
  85. package/lib/utils.mjs +104 -0
  86. package/lib/utils.mjs.map +1 -0
  87. package/package.json +89 -0
  88. package/src/auth.ts +89 -0
  89. package/src/gcpLogger.ts +124 -0
  90. package/src/gcpOpenTelemetry.ts +485 -0
  91. package/src/index.ts +59 -0
  92. package/src/metrics.ts +122 -0
  93. package/src/model-armor.ts +317 -0
  94. package/src/telemetry/action.ts +106 -0
  95. package/src/telemetry/defaults.ts +72 -0
  96. package/src/telemetry/engagement.ts +120 -0
  97. package/src/telemetry/feature.ts +170 -0
  98. package/src/telemetry/generate.ts +454 -0
  99. package/src/telemetry/path.ts +111 -0
  100. package/src/types.ts +133 -0
  101. package/src/utils.ts +175 -0
  102. package/tests/logs_no_input_output_test.ts +267 -0
  103. package/tests/logs_session_test.ts +219 -0
  104. package/tests/logs_test.ts +633 -0
  105. package/tests/metrics_test.ts +792 -0
  106. package/tests/model_armor_test.ts +336 -0
  107. package/tests/traces_test.ts +380 -0
  108. package/typedoc.json +3 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.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 { TraceFlags } from '@opentelemetry/api';\nimport type { ReadableSpan, TimedEvent } from '@opentelemetry/sdk-trace-base';\nimport { resolveCurrentPrincipal } from './auth.js';\n\n/**\n * The maximum length (in characters) of a logged input or output.\n * This limit exists to align the logs with GCP logging size limits.\n * */\nconst MAX_LOG_CONTENT_CHARS = 128_000;\n\n/**\n * The maximum length (in characters) of a flow path.\n */\nconst MAX_PATH_CHARS = 4096;\n\nexport function extractOuterFlowNameFromPath(path: string) {\n if (!path || path === '<unknown>') {\n return '<unknown>';\n }\n\n const flowName = path.match('/{(.+),t:flow}+');\n return flowName ? flowName[1] : '<unknown>';\n}\n\nexport function truncate(\n text: string,\n limit: number = MAX_LOG_CONTENT_CHARS\n): string {\n return text ? text.substring(0, limit) : text;\n}\n\nexport function truncatePath(path: string) {\n return truncate(path, MAX_PATH_CHARS);\n}\n\n/**\n * Extract first feature name from a path\n * e.g. for /{myFlow,t:flow}/{myStep,t:flowStep}/{googleai/gemini-pro,t:action,s:model}\n * returns \"myFlow\"\n */\nexport function extractOuterFeatureNameFromPath(path: string) {\n if (!path || path === '<unknown>') {\n return '<unknown>';\n }\n const first = path.split('/')[1];\n const featureName = first?.match(\n '{(.+),t:(flow|action|prompt|dotprompt|helper)'\n );\n return featureName ? featureName[1] : '<unknown>';\n}\n\nexport function extractErrorName(events: TimedEvent[]): string | undefined {\n return events\n .filter((event) => event.name === 'exception')\n .map((event) => {\n const attributes = event.attributes;\n return attributes\n ? truncate(attributes['exception.type'] as string, 1024)\n : '<unknown>';\n })\n .at(0);\n}\n\nexport function extractErrorMessage(events: TimedEvent[]): string | undefined {\n return events\n .filter((event) => event.name === 'exception')\n .map((event) => {\n const attributes = event.attributes;\n return attributes\n ? truncate(attributes['exception.message'] as string, 4096)\n : '<unknown>';\n })\n .at(0);\n}\n\nexport function extractErrorStack(events: TimedEvent[]): string | undefined {\n return events\n .filter((event) => event.name === 'exception')\n .map((event) => {\n const attributes = event.attributes;\n return attributes\n ? truncate(attributes['exception.stacktrace'] as string, 32_768)\n : '<unknown>';\n })\n .at(0);\n}\n\nexport function createCommonLogAttributes(\n span: ReadableSpan,\n projectId?: string\n) {\n const spanContext = span.spanContext();\n const isSampled = !!(spanContext.traceFlags & TraceFlags.SAMPLED);\n return {\n 'logging.googleapis.com/spanId': spanContext.spanId,\n 'logging.googleapis.com/trace': `projects/${projectId}/traces/${spanContext.traceId}`,\n 'logging.googleapis.com/trace_sampled': isSampled ? '1' : '0',\n };\n}\n\nexport function requestDenied(\n err: Error & {\n code?: number;\n statusDetails?: Record<string, any>[];\n }\n) {\n return err.code === 7;\n}\n\nexport function loggingDenied(\n err: Error & {\n code?: number;\n statusDetails?: Record<string, any>[];\n }\n) {\n return (\n requestDenied(err) &&\n err.statusDetails?.some((details) => {\n return details?.metadata?.permission === 'logging.logEntries.create';\n })\n );\n}\n\nexport function tracingDenied(\n err: Error & {\n code?: number;\n statusDetails?: Record<string, any>[];\n }\n) {\n // Looks like we don't get status details like we do with logging\n return requestDenied(err);\n}\n\nexport function metricsDenied(\n err: Error & {\n code?: number;\n statusDetails?: Record<string, any>[];\n }\n) {\n // Looks like we don't get status details like we do with logging\n return requestDenied(err);\n}\n\nexport async function permissionDeniedHelpText(role: string) {\n const principal = await resolveCurrentPrincipal();\n return `Add the role '${role}' to your Service Account in the IAM & Admin page on the Google Cloud console, or use the following command:\\n\\ngcloud projects add-iam-policy-binding ${principal.projectId ?? '${PROJECT_ID}'} \\\\\\n --member=serviceAccount:${principal.serviceAccountEmail || '${SERVICE_ACCT}'} \\\\\\n --role=${role}`;\n}\n\nexport async function loggingDeniedHelpText() {\n return permissionDeniedHelpText('roles/logging.logWriter');\n}\n\nexport async function tracingDeniedHelpText() {\n return permissionDeniedHelpText('roles/cloudtrace.agent');\n}\n\nexport async function metricsDeniedHelpText() {\n return permissionDeniedHelpText('roles/monitoring.metricWriter');\n}\n"],"mappings":"AAgBA,SAAS,kBAAkB;AAE3B,SAAS,+BAA+B;AAMxC,MAAM,wBAAwB;AAK9B,MAAM,iBAAiB;AAEhB,SAAS,6BAA6B,MAAc;AACzD,MAAI,CAAC,QAAQ,SAAS,aAAa;AACjC,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,KAAK,MAAM,iBAAiB;AAC7C,SAAO,WAAW,SAAS,CAAC,IAAI;AAClC;AAEO,SAAS,SACd,MACA,QAAgB,uBACR;AACR,SAAO,OAAO,KAAK,UAAU,GAAG,KAAK,IAAI;AAC3C;AAEO,SAAS,aAAa,MAAc;AACzC,SAAO,SAAS,MAAM,cAAc;AACtC;AAOO,SAAS,gCAAgC,MAAc;AAC5D,MAAI,CAAC,QAAQ,SAAS,aAAa;AACjC,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,CAAC;AAC/B,QAAM,cAAc,OAAO;AAAA,IACzB;AAAA,EACF;AACA,SAAO,cAAc,YAAY,CAAC,IAAI;AACxC;AAEO,SAAS,iBAAiB,QAA0C;AACzE,SAAO,OACJ,OAAO,CAAC,UAAU,MAAM,SAAS,WAAW,EAC5C,IAAI,CAAC,UAAU;AACd,UAAM,aAAa,MAAM;AACzB,WAAO,aACH,SAAS,WAAW,gBAAgB,GAAa,IAAI,IACrD;AAAA,EACN,CAAC,EACA,GAAG,CAAC;AACT;AAEO,SAAS,oBAAoB,QAA0C;AAC5E,SAAO,OACJ,OAAO,CAAC,UAAU,MAAM,SAAS,WAAW,EAC5C,IAAI,CAAC,UAAU;AACd,UAAM,aAAa,MAAM;AACzB,WAAO,aACH,SAAS,WAAW,mBAAmB,GAAa,IAAI,IACxD;AAAA,EACN,CAAC,EACA,GAAG,CAAC;AACT;AAEO,SAAS,kBAAkB,QAA0C;AAC1E,SAAO,OACJ,OAAO,CAAC,UAAU,MAAM,SAAS,WAAW,EAC5C,IAAI,CAAC,UAAU;AACd,UAAM,aAAa,MAAM;AACzB,WAAO,aACH,SAAS,WAAW,sBAAsB,GAAa,KAAM,IAC7D;AAAA,EACN,CAAC,EACA,GAAG,CAAC;AACT;AAEO,SAAS,0BACd,MACA,WACA;AACA,QAAM,cAAc,KAAK,YAAY;AACrC,QAAM,YAAY,CAAC,EAAE,YAAY,aAAa,WAAW;AACzD,SAAO;AAAA,IACL,iCAAiC,YAAY;AAAA,IAC7C,gCAAgC,YAAY,SAAS,WAAW,YAAY,OAAO;AAAA,IACnF,wCAAwC,YAAY,MAAM;AAAA,EAC5D;AACF;AAEO,SAAS,cACd,KAIA;AACA,SAAO,IAAI,SAAS;AACtB;AAEO,SAAS,cACd,KAIA;AACA,SACE,cAAc,GAAG,KACjB,IAAI,eAAe,KAAK,CAAC,YAAY;AACnC,WAAO,SAAS,UAAU,eAAe;AAAA,EAC3C,CAAC;AAEL;AAEO,SAAS,cACd,KAIA;AAEA,SAAO,cAAc,GAAG;AAC1B;AAEO,SAAS,cACd,KAIA;AAEA,SAAO,cAAc,GAAG;AAC1B;AAEA,eAAsB,yBAAyB,MAAc;AAC3D,QAAM,YAAY,MAAM,wBAAwB;AAChD,SAAO,iBAAiB,IAAI;AAAA;AAAA,yCAA0J,UAAU,aAAa,eAAe;AAAA,8BAAoC,UAAU,uBAAuB,iBAAiB;AAAA,aAAmB,IAAI;AAC3U;AAEA,eAAsB,wBAAwB;AAC5C,SAAO,yBAAyB,yBAAyB;AAC3D;AAEA,eAAsB,wBAAwB;AAC5C,SAAO,yBAAyB,wBAAwB;AAC1D;AAEA,eAAsB,wBAAwB;AAC5C,SAAO,yBAAyB,+BAA+B;AACjE;","names":[]}
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@frengki0707/google-cloud-clone",
3
+ "description": "Genkit AI framework plugin for Google Cloud Platform including Firestore trace/state store and deployment helpers for Cloud Functions for Firebase.",
4
+ "keywords": [
5
+ "genkit",
6
+ "genkit-plugin",
7
+ "genkit-telemetry",
8
+ "genkit-deploy",
9
+ "genkit-flow",
10
+ "google cloud",
11
+ "google cloud platform",
12
+ "ai",
13
+ "genai",
14
+ "generative-ai"
15
+ ],
16
+ "version": "1.33.0",
17
+ "type": "commonjs",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/genkit-ai/genkit.git",
21
+ "directory": "js/plugins/google-cloud"
22
+ },
23
+ "author": "genkit",
24
+ "license": "Apache-2.0",
25
+ "dependencies": {
26
+ "@google-cloud/logging-winston": "^6.0.0",
27
+ "@google-cloud/modelarmor": "^0.4.1",
28
+ "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.19.0",
29
+ "@google-cloud/opentelemetry-cloud-trace-exporter": "^2.4.1",
30
+ "@google-cloud/opentelemetry-resource-util": "^2.4.0",
31
+ "@opentelemetry/api": "^1.9.0",
32
+ "@opentelemetry/auto-instrumentations-node": "^0.49.1",
33
+ "@opentelemetry/core": "~1.25.0",
34
+ "@opentelemetry/instrumentation": "^0.52.0",
35
+ "@opentelemetry/instrumentation-pino": "^0.41.0",
36
+ "@opentelemetry/instrumentation-winston": "^0.39.0",
37
+ "@opentelemetry/resources": "~1.25.0",
38
+ "@opentelemetry/sdk-metrics": "~1.25.0",
39
+ "@opentelemetry/sdk-node": "^0.52.0",
40
+ "@opentelemetry/sdk-trace-base": "~1.25.0",
41
+ "google-auth-library": "^9.6.3",
42
+ "node-fetch": "^3.3.2",
43
+ "winston": "^3.12.0"
44
+ },
45
+ "peerDependencies": {
46
+ "genkit": "^1.33.0"
47
+ },
48
+ "devDependencies": {
49
+ "@jest/globals": "^29.7.0",
50
+ "@types/node": "^20.11.16",
51
+ "jest": "^29.7.0",
52
+ "npm-run-all": "^4.1.5",
53
+ "rimraf": "^6.0.1",
54
+ "ts-jest": "^29.1.2",
55
+ "tsup": "^8.3.5",
56
+ "tsx": "^4.19.2",
57
+ "typescript": "^5.9.3"
58
+ },
59
+ "types": "./lib/index.d.ts",
60
+ "exports": {
61
+ ".": {
62
+ "require": "./lib/index.js",
63
+ "import": "./lib/index.mjs",
64
+ "types": "./lib/index.d.ts",
65
+ "default": "./lib/index.js"
66
+ },
67
+ "./model-armor": {
68
+ "require": "./lib/model-armor.js",
69
+ "import": "./lib/model-armor.mjs",
70
+ "types": "./lib/model-armor.d.ts",
71
+ "default": "./lib/model-armor.js"
72
+ }
73
+ },
74
+ "typesVersions": {
75
+ "*": {
76
+ "model-armor": [
77
+ "lib/model-armor"
78
+ ]
79
+ }
80
+ },
81
+ "scripts": {
82
+ "check": "tsc",
83
+ "compile": "tsup-node",
84
+ "build:clean": "rimraf ./lib",
85
+ "build": "npm-run-all build:clean check compile",
86
+ "build:watch": "tsup-node --watch",
87
+ "test": "node node_modules/jest/bin/jest --runInBand --verbose"
88
+ }
89
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Copyright 2024 Google LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { logger } from 'genkit/logging';
17
+ import { GoogleAuth, auth, type CredentialBody } from 'google-auth-library';
18
+ import type { GcpPrincipal, GcpTelemetryConfig } from './types.js';
19
+
20
+ /**
21
+ * Allows Google Cloud credentials to be to passed in "raw" as an environment
22
+ * variable. This is helpful in environments where the developer has limited
23
+ * ability to configure their compute environment, but does have the ablilty to
24
+ * set environment variables.
25
+ *
26
+ * This is different from the GOOGLE_APPLICATION_CREDENTIALS used by ADC, which
27
+ * represents a path to a credential file on disk. In *most* cases, even for
28
+ * 3rd party cloud providers, developers *should* attempt to use ADC, which
29
+ * searches for credential files in standard locations, before using this
30
+ * method.
31
+ *
32
+ * See also: https://github.com/googleapis/google-auth-library-nodejs?tab=readme-ov-file#loading-credentials-from-environment-variables
33
+ */
34
+ export async function credentialsFromEnvironment(): Promise<
35
+ Partial<GcpTelemetryConfig>
36
+ > {
37
+ let authClient: GoogleAuth;
38
+ const options: Partial<GcpTelemetryConfig> = {};
39
+
40
+ if (process.env.GCLOUD_SERVICE_ACCOUNT_CREDS) {
41
+ logger.debug('Retrieving credentials from GCLOUD_SERVICE_ACCOUNT_CREDS');
42
+ const serviceAccountCreds = JSON.parse(
43
+ process.env.GCLOUD_SERVICE_ACCOUNT_CREDS
44
+ );
45
+ const authOptions = { credentials: serviceAccountCreds };
46
+ authClient = new GoogleAuth(authOptions);
47
+ options.credentials = await authClient.getCredentials();
48
+ } else {
49
+ authClient = new GoogleAuth();
50
+ }
51
+ try {
52
+ const projectId = await authClient.getProjectId();
53
+ if (projectId && projectId.length > 0) {
54
+ options.projectId = projectId;
55
+ }
56
+ } catch (error) {
57
+ logger.warn(error);
58
+ }
59
+ return options;
60
+ }
61
+
62
+ /**
63
+ * Resolve the currently configured principal, either from the Genkit specific
64
+ * GCLOUD_SERVICE_ACCOUNT_CREDS environment variable, or from ADC.
65
+ *
66
+ * Since the Google Cloud Telemetry Exporter will discover credentials on its
67
+ * own, we don't immediately have access to the current principal. This method
68
+ * can be handy to get access to the current credential for logging debugging
69
+ * information or other purposes.
70
+ **/
71
+ export async function resolveCurrentPrincipal(): Promise<GcpPrincipal> {
72
+ const envCredentials = await credentialsFromEnvironment();
73
+ let adcCredentials = {} as CredentialBody;
74
+ try {
75
+ adcCredentials = await auth.getCredentials();
76
+ } catch (e) {
77
+ logger.debug('Could not retrieve client_email from ADC.');
78
+ }
79
+
80
+ // TODO(michaeldoyle): How to look up if the user provided credentials in the
81
+ // plugin config (i.e. GcpTelemetryOptions)
82
+ const serviceAccountEmail =
83
+ envCredentials.credentials?.client_email ?? adcCredentials.client_email;
84
+
85
+ return {
86
+ projectId: envCredentials.projectId,
87
+ serviceAccountEmail,
88
+ };
89
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Copyright 2024 Google LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { LoggingWinston } from '@google-cloud/logging-winston';
18
+ import { getCurrentEnv } from 'genkit';
19
+ import { logger } from 'genkit/logging';
20
+ import type { Writable } from 'stream';
21
+ import type { GcpTelemetryConfig } from './types.js';
22
+ import { loggingDenied, loggingDeniedHelpText } from './utils.js';
23
+
24
+ /**
25
+ * Additional streams for writing log data to. Useful for unit testing.
26
+ */
27
+ let additionalStream: Writable;
28
+ let useJsonFormatOverride = false;
29
+
30
+ /**
31
+ * Provides a logger for exporting Genkit debug logs to GCP Cloud
32
+ * logs.
33
+ */
34
+ export class GcpLogger {
35
+ constructor(private readonly config: GcpTelemetryConfig) {}
36
+
37
+ async getLogger(env: string) {
38
+ // Dynamically importing winston here more strictly controls
39
+ // the import order relative to registering instrumentation
40
+ // with OpenTelemetry. Incorrect import order will trigger
41
+ // an internal OT warning and will result in logs not being
42
+ // associated with correct spans/traces.
43
+ const winston = await import('winston');
44
+
45
+ const format =
46
+ useJsonFormatOverride || this.shouldExport(env)
47
+ ? { format: winston.format.json() }
48
+ : {
49
+ format: winston.format.printf((info): string => {
50
+ return `[${info.level}] ${info.message}`;
51
+ }),
52
+ };
53
+
54
+ const transports: any[] = [];
55
+ transports.push(
56
+ this.shouldExport(env)
57
+ ? new LoggingWinston({
58
+ labels: { module: 'genkit' },
59
+ prefix: 'genkit',
60
+ logName: 'genkit_log',
61
+ projectId: this.config.projectId,
62
+ credentials: this.config.credentials,
63
+ autoRetry: true,
64
+ defaultCallback: await this.getErrorHandler(),
65
+ })
66
+ : new winston.transports.Console()
67
+ );
68
+ if (additionalStream) {
69
+ transports.push(
70
+ new winston.transports.Stream({ stream: additionalStream })
71
+ );
72
+ }
73
+ return winston.createLogger({
74
+ transports: transports,
75
+ ...format,
76
+ exceptionHandlers: [new winston.transports.Console()],
77
+ });
78
+ }
79
+
80
+ private async getErrorHandler(): Promise<(err: Error | null) => void> {
81
+ // only log the first time
82
+ let instructionsLogged = false;
83
+ const helpInstructions = await loggingDeniedHelpText();
84
+
85
+ return async (err: Error | null) => {
86
+ // Use the defaultLogger so that logs don't get swallowed by
87
+ // the open telemetry exporter
88
+ const defaultLogger = logger.defaultLogger;
89
+ if (err && loggingDenied(err)) {
90
+ if (!instructionsLogged) {
91
+ instructionsLogged = true;
92
+ defaultLogger.error(
93
+ `Unable to send logs to Google Cloud: ${err.message}\n\n${helpInstructions}\n`
94
+ );
95
+ }
96
+ } else if (err) {
97
+ defaultLogger.error(`Unable to send logs to Google Cloud: ${err}`);
98
+ }
99
+
100
+ if (err) {
101
+ // Assume the logger is compromised, and we need a new one
102
+ // Reinitialize the genkit logger with a new instance with the same config
103
+ logger.init(
104
+ await new GcpLogger(this.config).getLogger(getCurrentEnv())
105
+ );
106
+ defaultLogger.info('Initialized a new GcpLogger.');
107
+ }
108
+ };
109
+ }
110
+
111
+ private shouldExport(env?: string) {
112
+ return this.config.export;
113
+ }
114
+ }
115
+
116
+ /** @hidden */
117
+ export function __addTransportStreamForTesting(stream: Writable) {
118
+ additionalStream = stream;
119
+ }
120
+
121
+ /** @hidden */
122
+ export function __useJsonFormatForTesting() {
123
+ useJsonFormatOverride = true;
124
+ }