Browse Source

Merge pull request #8667 from weseek/feat/143808-can-send-traces-with-otel

feat: refs #143808/134809: OpenTelemetry で GROWI から Trace/Metrics を吐き出せる
Yuki Takei 2 years ago
parent
commit
d76401215c

+ 10 - 0
.devcontainer/docker-compose.yml

@@ -26,6 +26,10 @@ services:
       - ../../growi-docker-compose:/workspace/growi-docker-compose:delegated
       - ../../growi-docker-compose:/workspace/growi-docker-compose:delegated
 
 
     tty: true
     tty: true
+    networks:
+    - default
+    # https://redmine.weseek.co.jp/issues/144306 で整備予定
+    # - opentelemetry-collector-dev-setup_default
 
 
   mongo:
   mongo:
     image: mongo:6.0
     image: mongo:6.0
@@ -63,3 +67,9 @@ volumes:
   node_modules_app:
   node_modules_app:
   node_modules_slackbot-proxy:
   node_modules_slackbot-proxy:
   buildcache_app:
   buildcache_app:
+
+networks:
+  default:
+  # https://redmine.weseek.co.jp/issues/144306 で整備予定
+  # opentelemetry-collector-dev-setup_default:
+  #   external: true

+ 7 - 0
apps/app/package.json

@@ -81,6 +81,13 @@
     "@growi/remark-lsx": "link:../../packages/remark-lsx",
     "@growi/remark-lsx": "link:../../packages/remark-lsx",
     "@growi/slack": "link:../../packages/slack",
     "@growi/slack": "link:../../packages/slack",
     "@keycloak/keycloak-admin-client": "^18.0.0",
     "@keycloak/keycloak-admin-client": "^18.0.0",
+    "@opentelemetry/api": "^1.8.0",
+    "@opentelemetry/auto-instrumentations-node": "^0.44.0",
+    "@opentelemetry/exporter-metrics-otlp-grpc": "^0.50.0",
+    "@opentelemetry/exporter-trace-otlp-grpc": "^0.50.0",
+    "@opentelemetry/sdk-metrics": "^1.23.0",
+    "@opentelemetry/sdk-node": "^0.50.0",
+    "@opentelemetry/sdk-trace-node": "^1.23.0",
     "@slack/web-api": "^6.2.4",
     "@slack/web-api": "^6.2.4",
     "@slack/webhook": "^6.0.0",
     "@slack/webhook": "^6.0.0",
     "@types/jest": "^29.5.2",
     "@types/jest": "^29.5.2",

+ 6 - 0
apps/app/src/server/crowi/index.js

@@ -40,6 +40,8 @@ import { UserNotificationService } from '../service/user-notification';
 import { instantiateYjsConnectionManager } from '../service/yjs-connection-manager';
 import { instantiateYjsConnectionManager } from '../service/yjs-connection-manager';
 import { getMongoUri, mongoOptions } from '../util/mongoose-utils';
 import { getMongoUri, mongoOptions } from '../util/mongoose-utils';
 
 
+import { OpenTelemetry } from './opentelemetry';
+
 
 
 const logger = loggerFactory('growi:crowi');
 const logger = loggerFactory('growi:crowi');
 const httpErrorHandler = require('../middlewares/http-error-handler');
 const httpErrorHandler = require('../middlewares/http-error-handler');
@@ -456,6 +458,10 @@ Crowi.prototype.start = async function() {
   await this.init();
   await this.init();
   await this.buildServer();
   await this.buildServer();
 
 
+  // 具体的な設定値については、https://redmine.weseek.co.jp/issues/144351 で決定予定
+  const otel = new OpenTelemetry('next-app', 'growi-app-XXX', this.version);
+  otel.startInstrumentation();
+
   // setup Next.js
   // setup Next.js
   this.nextApp = next({ dev });
   this.nextApp = next({ dev });
   await this.nextApp.prepare();
   await this.nextApp.prepare();

+ 77 - 0
apps/app/src/server/crowi/opentelemetry.ts

@@ -0,0 +1,77 @@
+import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
+import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
+import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
+import { Resource } from '@opentelemetry/resources';
+import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
+import type { NodeSDKConfiguration } from '@opentelemetry/sdk-node';
+import { NodeSDK } from '@opentelemetry/sdk-node';
+import { TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-node';
+import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_INSTANCE_ID, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
+
+export class OpenTelemetry {
+
+  name: string;
+
+  instanceId: string;
+
+  version: string;
+
+  sdkInstance: NodeSDK;
+
+  constructor(name: string, instanceId: string, version: string) {
+    this.name = name;
+    this.instanceId = instanceId;
+    this.version = version;
+  }
+
+  private generateNodeSDKConfiguration(): Partial<NodeSDKConfiguration> {
+    return {
+      resource: new Resource({
+        [SEMRESATTRS_SERVICE_NAME]: 'next-app',
+        // TODO: 環境変数から入れられるようにしたい
+        // https://redmine.weseek.co.jp/issues/144352 で実施予定
+        [SEMRESATTRS_SERVICE_INSTANCE_ID]: this.instanceId,
+        [SEMRESATTRS_SERVICE_VERSION]: this.version,
+      }),
+      // TODO: 宛先を環境変数から設定できるようにしたい
+      // https://redmine.weseek.co.jp/issues/144352 で実施予定
+      traceExporter: new OTLPTraceExporter({ url: 'http://otel-collector:4317' }),
+      metricReader: new PeriodicExportingMetricReader({
+        // TODO: 宛先を環境変数から設定できるようにしたい
+        // https://redmine.weseek.co.jp/issues/144352 で実施予定
+        exporter: new OTLPMetricExporter({ url: 'http://otel-collector:4317' }),
+        exportIntervalMillis: 10000,
+      }),
+      instrumentations: [getNodeAutoInstrumentations({
+        // この module は大量の trace を生成するため、無効化する
+        // see: https://opentelemetry.io/docs/languages/js/libraries/#registration
+        '@opentelemetry/instrumentation-fs': {
+          enabled: false,
+        },
+      })],
+      // 全 trace の半分を出す
+      // see: https://opentelemetry.io/docs/languages/js/sampling/
+      sampler: new TraceIdRatioBasedSampler(0.5),
+    };
+  }
+
+  public startInstrumentation(): void {
+    this.sdkInstance = new NodeSDK(this.generateNodeSDKConfiguration());
+    this.sdkInstance.start();
+  }
+
+  public async shutdownInstrumentation(): Promise<void> {
+    await this.sdkInstance.shutdown();
+
+    // メモ: 以下の restart コードは動かない
+    // span/metrics ともに何も出なくなる
+    // そもそも、restart するような使い方が出来なさそう?
+    // see: https://github.com/open-telemetry/opentelemetry-specification/issues/27/
+    // const sdk = new NodeSDK({...});
+    // sdk.start();
+    // await sdk.shutdown().catch(console.error);
+    // const newSdk = new NodeSDK({...});
+    // newSdk.start();
+  }
+
+}

File diff suppressed because it is too large
+ 935 - 0
yarn.lock


Some files were not shown because too many files changed in this diff