From 624d1a9ba0d20d7ea69bfa5b3999d1e9fb2d8d80 Mon Sep 17 00:00:00 2001 From: "Connell, Joseph" Date: Fri, 23 May 2025 22:19:28 -0700 Subject: [PATCH 1/4] Added otel enabled Docker Compose file. Otel Enabled on lowcoder-api-service. Updated .sh files from CRLF to LF. Updated DockerFile to include OpenTelemetry JavaAgent. --- deploy/docker/Dockerfile | 7 + deploy/docker/docker-compose-multi-otel.yaml | 205 +++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 deploy/docker/docker-compose-multi-otel.yaml diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index 5ecbbd579d..da06c12e20 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -14,6 +14,10 @@ RUN mkdir -p /lowcoder/api-service/config /lowcoder/api-service/logs /lowcoder/p # Copy lowcoder server configuration COPY server/api-service/lowcoder-server/src/main/resources/application.yaml /lowcoder/api-service/config/ +# Add Opentelemetry agent +RUN curl -L -o /lowcoder/api-service/opentelemetry-javaagent.jar \ + "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar" + # Add bootstrapfile COPY deploy/docker/api-service/entrypoint.sh /lowcoder/api-service/entrypoint.sh COPY deploy/docker/api-service/init.sh /lowcoder/api-service/init.sh @@ -43,6 +47,9 @@ COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder-server/lowcode COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder-server/lowcoder-server/target/lowcoder-api-service-bin/libs /lowcoder/api-service/libs COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder-server/lowcoder-server/target/lowcoder-api-service-bin/plugins /lowcoder/api-service/plugins +#Define OpenTelemetry Java agent +ENV JAVA_TOOL_OPTIONS="-javaagent:/lowcoder/api-service/opentelemetry-javaagent.jar" + EXPOSE 8080 CMD [ "/bin/bash" , "/lowcoder/api-service/entrypoint.sh" ] diff --git a/deploy/docker/docker-compose-multi-otel.yaml b/deploy/docker/docker-compose-multi-otel.yaml new file mode 100644 index 0000000000..132f9ba7da --- /dev/null +++ b/deploy/docker/docker-compose-multi-otel.yaml @@ -0,0 +1,205 @@ +name: lowcoder-ce + +services: + + ## + ## Start services required for Lowcoder (MongoDB and Redis) + ## + mongodb: + image: "mongo:7.0" + container_name: mongodb + environment: + MONGO_INITDB_DATABASE: lowcoder + MONGO_INITDB_ROOT_USERNAME: lowcoder + MONGO_INITDB_ROOT_PASSWORD: secret123 + volumes: + - ./lowcoder-stacks/data/mongodb:/data/db + restart: unless-stopped + healthcheck: # https://github.com/rodrigobdz/docker-compose-healthchecks?tab=readme-ov-file#mongo + test: + [ + "CMD", + "mongosh", + "--quiet", + "127.0.0.1/test", + "--eval", + "'quit(db.runCommand({ ping: 1 }).ok ? 0 : 2)'", + ] + interval: 5s + timeout: 10s + retries: 10 + start_period: 40s + + redis: + image: redis:7-alpine + container_name: redis + restart: unless-stopped + healthcheck: # https://stackoverflow.com/a/71504657 + test: ["CMD-SHELL", "redis-cli ping | grep PONG"] + interval: 1s + timeout: 3s + retries: 10 + + + ## + ## Start Lowcoder backend services (api-service and node-service) + ## + lowcoder-api-service: + image: lowcoder-ce-api-service-jmc:latest + container_name: lowcoder-api-service + # Enabled ports to be able to access backend from host + # ports: + # - "8080:8080" + environment: + LOWCODER_PUBLIC_URL: "http://localhost:3000/" + LOWCODER_PUID: "9001" + LOWCODER_PGID: "9001" + LOWCODER_MONGODB_URL: "mongodb://lowcoder:secret123@mongodb/lowcoder?authSource=admin" + LOWCODER_REDIS_URL: "redis://redis:6379" + LOWCODER_NODE_SERVICE_URL: "http://lowcoder-node-service:6060" + LOWCODER_MAX_QUERY_TIMEOUT: 120 + LOWCODER_MAX_REQUEST_SIZE: 20m + LOWCODER_EMAIL_AUTH_ENABLED: "true" + LOWCODER_EMAIL_SIGNUP_ENABLED: "true" + LOWCODER_CREATE_WORKSPACE_ON_SIGNUP: "true" + # + # ! PLEASE CHANGE THESE TO SOMETHING UNIQUE ! + # + # LOWCODER_DB_ENCRYPTION_PASSWORD and LOWCODER_DB_ENCRYPTION_SALT is used + # to encrypt sensitive data in database so it is important to change the defaults + # + LOWCODER_DB_ENCRYPTION_PASSWORD: "lowcoder.org" + LOWCODER_DB_ENCRYPTION_SALT: "lowcoder.org" + LOWCODER_CORS_DOMAINS: "*" + LOWCODER_MAX_ORGS_PER_USER: 100 + LOWCODER_MAX_MEMBERS_PER_ORG: 1000 + LOWCODER_MAX_GROUPS_PER_ORG: 100 + LOWCODER_MAX_APPS_PER_ORG: 1000 + LOWCODER_MAX_DEVELOPERS: 50 + # + # API-KEY secret - should be a string of at least 32 random characters + # - on linux/mac, generate one eg. with: head /dev/urandom | head -c 30 | shasum -a 256 + # + LOWCODER_API_KEY_SECRET: "5a41b090758b39b226603177ef48d73ae9839dd458ccb7e66f7e7cc028d5a50b" + LOWCODER_PLUGINS_DIR: "../plugins" + LOWCODER_API_RATE_LIMIT: 50 + LOWCODER_WORKSPACE_MODE: SAAS + LOWCODER_MARKETPLACE_PRIVATE_MODE: "true" + # Lowcoder notification emails setup + LOWCODER_ADMIN_SMTP_HOST: smtp.gmail.com + LOWCODER_ADMIN_SMTP_PORT: 587 + LOWCODER_ADMIN_SMTP_USERNAME: + LOWCODER_ADMIN_SMTP_PASSWORD: + LOWCODER_ADMIN_SMTP_AUTH: "true" + LOWCODER_ADMIN_SMTP_SSL_ENABLED: "false" + LOWCODER_ADMIN_SMTP_STARTTLS_ENABLED: "true" + LOWCODER_ADMIN_SMTP_STARTTLS_REQUIRED: "true" + # Email used as sender in lost password email + LOWCODER_EMAIL_NOTIFICATIONS_SENDER: info@localhost + # Lowcoder superuser details + LOWCODER_SUPERUSER_USERNAME: admin@localhost + # If left blank, a password will be generated and written into api-service log + LOWCODER_SUPERUSER_PASSWORD: + + # OpenTelemetry Related Settings + # OTEL_JAVAAGENT_ENABLED: "false" + OTEL_RESOURCE_ATTRIBUTES: "service.name=lowcoder-api-service,service.version=2.6.5,deployment.environment=production" + OTEL_SERVICE_NAME: "lowcoder-api-service" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-lgtm:4317" + OTEL_TRACES_EXPORTER: "otlp" + OTEL_METRICS_EXPORTER: "otlp" + OTEL_LOGS_EXPORTER: "otlp" + + + restart: unless-stopped + depends_on: + mongodb: + condition: service_healthy + restart: true + redis: + condition: service_healthy + restart: true + otel-lgtm: + condition: service_healthy + restart: true + volumes: + - ./lowcoder-stacks:/lowcoder-stacks + - ./lowcoder-stacks/assets:/lowcoder/assets + healthcheck: #https://stackoverflow.com/questions/71101967/how-should-i-use-grep-in-docker-compose-healthcheck + test: curl -sS http://lowcoder-api-service:8080 | grep -c "Lowcoder API is up and runnig" > /dev/null + interval: 3s + timeout: 5s + retries: 10 + + + lowcoder-node-service: + image: lowcoder-ce-node-service-jmc:latest + container_name: lowcoder-node-service + # Enabled ports to be able to access backend from host + # ports: + # - "6060:6060" + environment: + LOWCODER_PUID: "9001" + LOWCODER_PGID: "9001" + LOWCODER_API_SERVICE_URL: "http://lowcoder-api-service:8080" + restart: unless-stopped + depends_on: + lowcoder-api-service: + condition: service_healthy + restart: true + healthcheck: #https://stackoverflow.com/questions/71101967/how-should-i-use-grep-in-docker-compose-healthcheck + test: curl -sS http://lowcoder-node-service:6060 | grep -c "Lowcoder Node Service is up and running" > /dev/null + interval: 3s + timeout: 5s + retries: 10 + + ## + ## Start Lowcoder web frontend + ## + lowcoder-frontend: + image: lowcoder-ce-frontend-jmc:latest + container_name: lowcoder-frontend + ports: + - "3000:3000" + environment: + LOWCODER_PUID: "9001" + LOWCODER_PGID: "9001" + LOWCODER_MAX_REQUEST_SIZE: 20m + LOWCODER_MAX_QUERY_TIMEOUT: 120 + LOWCODER_API_SERVICE_URL: "http://lowcoder-api-service:8080" + LOWCODER_NODE_SERVICE_URL: "http://lowcoder-node-service:6060" + restart: unless-stopped + depends_on: + lowcoder-node-service: + condition: service_healthy + restart: true + lowcoder-api-service: + condition: service_healthy + restart: true + volumes: + - ./lowcoder-stacks/assets:/lowcoder/assets + healthcheck: + test: curl --fail http://lowcoder-frontend:3000 || exit 1 + interval: 5s + retries: 10 + start_period: 10s + timeout: 10s + otel-lgtm: + image: grafana/otel-lgtm:latest + container_name: otel-lgtm + ports: + - "3001:3000" # Grafana UI + - "4317:4317" # OTLP gRPC receiver + - "4318:4318" # OTLP HTTP receiver + - "9090:9090" # Prometheus / Mimir + - "3100:3100" # Loki + - "3200:3200" # Tempo + volumes: + - ./otel-lgtm-data:/data # Persistent storage for Grafana, Loki, Tempo, Mimir + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3001/api/health"] # Check Grafana UI health + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s # Give it some time to start up initially From 6b2001f2169b969fc4779a1543a7d51bc46d05b3 Mon Sep 17 00:00:00 2001 From: "Connell, Joseph" Date: Sat, 24 May 2025 17:53:32 -0700 Subject: [PATCH 2/4] Added Node Service Otel-Auto Instrumentation --- deploy/docker/.dockerignore | 45 ++++++ deploy/docker/Dockerfile | 3 +- deploy/docker/docker-compose-multi-otel.yaml | 22 ++- deploy/docker/node-service/entrypoint.sh | 3 + deploy/docker/node-service/otel-config.js | 139 +++++++++++++++++++ server/node-service/.gitignore | 2 +- 6 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 deploy/docker/.dockerignore create mode 100644 deploy/docker/node-service/otel-config.js diff --git a/deploy/docker/.dockerignore b/deploy/docker/.dockerignore new file mode 100644 index 0000000000..b874686d2c --- /dev/null +++ b/deploy/docker/.dockerignore @@ -0,0 +1,45 @@ +# Node.js dependencies +node_modules +npm-debug.log +yarn-error.log + +# Logs +logs +*.log + +# Build artifacts +dist +build +*.tsbuildinfo + +# Environment files +.env +.env.local +.env.*.local + +# IDE and editor files +.vscode/ +.idea/ +*.swp + +# OS files +.DS_Store +Thumbs.db + +# Docker-related files +.dockerignore +Dockerfile* + +# Temporary files +tmp/ +temp/ +*.tmp +*.temp + +# Test files +coverage/ +*.test.js +*.spec.js +*.test.ts +*.spec.ts +jest.config.js diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index da06c12e20..3eca75d472 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -78,9 +78,10 @@ WORKDIR /lowcoder/node-service/app/ RUN yarn --immutable RUN yarn build -# Copy startup script +# Copy startup script and OpenTelemetry config COPY deploy/docker/node-service/entrypoint.sh /lowcoder/node-service/entrypoint.sh COPY deploy/docker/node-service/init.sh /lowcoder/node-service/init.sh +COPY deploy/docker/node-service/otel-config.js /lowcoder/node-service/otel-config.js RUN chmod +x /lowcoder/node-service/*.sh ## diff --git a/deploy/docker/docker-compose-multi-otel.yaml b/deploy/docker/docker-compose-multi-otel.yaml index 132f9ba7da..c4b5191d53 100644 --- a/deploy/docker/docker-compose-multi-otel.yaml +++ b/deploy/docker/docker-compose-multi-otel.yaml @@ -103,8 +103,9 @@ services: # OpenTelemetry Related Settings # OTEL_JAVAAGENT_ENABLED: "false" - OTEL_RESOURCE_ATTRIBUTES: "service.name=lowcoder-api-service,service.version=2.6.5,deployment.environment=production" + OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=production" OTEL_SERVICE_NAME: "lowcoder-api-service" + OTEL_SERVICE_VERSION: "2.6.5" OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-lgtm:4317" OTEL_TRACES_EXPORTER: "otlp" @@ -143,6 +144,23 @@ services: LOWCODER_PUID: "9001" LOWCODER_PGID: "9001" LOWCODER_API_SERVICE_URL: "http://lowcoder-api-service:8080" + + ## + # OpenTelemetry Related Settings + # + # Uncomment OTEL_NODE_ENABLED_INSTRUMENTATIONS + # to disable OpenTelemetry Node agent + ## + # OTEL_NODE_ENABLED_INSTRUMENTATIONS="" + OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=production" + OTEL_SERVICE_NAME: "lowcoder-node-service" + OTEL_SERVICE_VERSION: "2.6.5" + OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" + OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-lgtm:4317" + OTEL_TRACES_EXPORTER: "otlp" + OTEL_METRICS_EXPORTER: "otlp" + OTEL_LOGS_EXPORTER: "otlp" + restart: unless-stopped depends_on: lowcoder-api-service: @@ -153,7 +171,7 @@ services: interval: 3s timeout: 5s retries: 10 - + ## ## Start Lowcoder web frontend ## diff --git a/deploy/docker/node-service/entrypoint.sh b/deploy/docker/node-service/entrypoint.sh index 33308e4c7b..bd2bafc13c 100755 --- a/deploy/docker/node-service/entrypoint.sh +++ b/deploy/docker/node-service/entrypoint.sh @@ -23,4 +23,7 @@ if [ "$(id -u)" -eq 0 ]; then fi echo +# Require OpenTelemetry configuration +export NODE_OPTIONS="-r ./otel-config.js" + exec $GOSU yarn start diff --git a/deploy/docker/node-service/otel-config.js b/deploy/docker/node-service/otel-config.js new file mode 100644 index 0000000000..05b2128747 --- /dev/null +++ b/deploy/docker/node-service/otel-config.js @@ -0,0 +1,139 @@ +/** + * OpenTelemetry Configuration for Lowcoder Application + * + * This file sets up OpenTelemetry auto-instrumentation for traces and metrics. + * + * How to use: + * 1. Save this file as `otel-config.js` in your application's root directory. + * 2. Ensure you have the necessary OpenTelemetry packages installed: + * @opentelemetry/sdk-node + * @opentelemetry/api + * @opentelemetry/exporter-trace-otlp-http (or -grpc) + * @opentelemetry/exporter-metrics-otlp-http (or -grpc) + * @opentelemetry/resources + * @opentelemetry/semantic-conventions + * @opentelemetry/auto-instrumentations-node (CRITICAL for auto-instrumentation) + * + * Install @opentelemetry/auto-instrumentations-node if you haven't: + * `npm install @opentelemetry/auto-instrumentations-node` + * or + * `yarn add @opentelemetry/auto-instrumentations-node` + * + * 3. Start your application using the `-r` flag to preload this configuration: + * `export NODE_OPTIONS="-r ./otel-config.js"` + * + * Environment Variables for Configuration: + * - OTEL_EXPORTER_OTLP_ENDPOINT: Base URL for OTLP HTTP exporters (e.g., http://localhost:4318). + * If not set, defaults to http://localhost:4318. + * - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: Specific URL for OTLP HTTP traces exporter (e.g., http://localhost:4318/v1/traces). + * - OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: Specific URL for OTLP HTTP metrics exporter (e.g., http://localhost:4318/v1/metrics). + * - OTEL_SERVICE_NAME: Name of your service (e.g., 'node-service'). Defaults to 'unknown_service:nodejs'. + * - OTEL_LOG_LEVEL: Set OpenTelemetry diagnostic logging level (e.g., 'debug', 'info', 'warn', 'error'). + * - OTEL_EXPORTER_PROTOCOL: 'http/protobuf' (default) or 'grpc'. + */ + +const process = require('process'); +const { NodeSDK } = require('@opentelemetry/sdk-node'); +const { OTLPTraceExporter: OTLPTraceExporterHttp } = require('@opentelemetry/exporter-trace-otlp-http'); +const { OTLPTraceExporter: OTLPTraceExporterGrpc } = require('@opentelemetry/exporter-trace-otlp-grpc'); +const { OTLPMetricExporter: OTLPMetricExporterHttp } = require('@opentelemetry/exporter-metrics-otlp-http'); +const { OTLPMetricExporter: OTLPMetricExporterGrpc } = require('@opentelemetry/exporter-metrics-otlp-grpc'); +const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-node'); // Using BatchSpanProcessor for better performance +const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics'); +const { Resource } = require('@opentelemetry/resources'); +const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions'); +const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node'); +const { DiagConsoleLogger, DiagLogLevel, diag } = require('@opentelemetry/api'); + +// --- Configuration --- +const SERVICE_NAME = process.env.OTEL_SERVICE_NAME || 'lowcoder-node-service'; +const OTLP_EXPORTER_PROTOCOL = process.env.OTEL_EXPORTER_PROTOCOL || 'http/protobuf'; // 'http/protobuf' or 'grpc' + +const DEFAULT_OTLP_HTTP_ENDPOINT = 'http://localhost:4318'; +const DEFAULT_OTLP_GRPC_ENDPOINT = 'http://localhost:4317'; + +const OTLP_ENDPOINT = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || + (OTLP_EXPORTER_PROTOCOL === 'grpc' ? DEFAULT_OTLP_GRPC_ENDPOINT : DEFAULT_OTLP_HTTP_ENDPOINT); + +const TRACES_ENDPOINT = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || + (OTLP_EXPORTER_PROTOCOL === 'grpc' ? OTLP_ENDPOINT : `${OTLP_ENDPOINT}/v1/traces`); +const METRICS_ENDPOINT = process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT || + (OTLP_EXPORTER_PROTOCOL === 'grpc' ? OTLP_ENDPOINT : `${OTLP_ENDPOINT}/v1/metrics`); + +// Optional: Set OpenTelemetry diagnostic logging level +const otelLogLevel = process.env.OTEL_LOG_LEVEL?.toUpperCase(); +if (otelLogLevel && DiagLogLevel[otelLogLevel]) { + diag.setLogger(new DiagConsoleLogger(), DiagLogLevel[otelLogLevel]); +} else { + diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO); // Default to INFO +} + +diag.info(`OpenTelemetry SDK configured for service: ${SERVICE_NAME}`); +diag.info(`Using OTLP protocol: ${OTLP_EXPORTER_PROTOCOL}`); +diag.info(`Traces Exporter Endpoint: ${TRACES_ENDPOINT}`); +diag.info(`Metrics Exporter Endpoint: ${METRICS_ENDPOINT}`); + +// --- Resource Definition --- +const resource = Resource.default().merge( + new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: SERVICE_NAME, + // [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0', // Optional + }) +); + +// --- Exporter Configuration --- +let traceExporter; +let metricExporter; + +if (OTLP_EXPORTER_PROTOCOL === 'grpc') { + diag.info('Using gRPC Exporters'); + traceExporter = new OTLPTraceExporterGrpc({ url: TRACES_ENDPOINT }); + metricExporter = new OTLPMetricExporterGrpc({ url: METRICS_ENDPOINT }); +} else { + diag.info('Using HTTP/protobuf Exporters'); + traceExporter = new OTLPTraceExporterHttp({ url: TRACES_ENDPOINT }); + metricExporter = new OTLPMetricExporterHttp({ url: METRICS_ENDPOINT }); +} + +// --- SDK Initialization --- +const sdk = new NodeSDK({ + resource: resource, + traceExporter: traceExporter, + spanProcessor: new BatchSpanProcessor(traceExporter), // Recommended for most cases + metricReader: new PeriodicExportingMetricReader({ + exporter: metricExporter, + exportIntervalMillis: 10000, // Export metrics every 10 seconds + }), + instrumentations: [ + getNodeAutoInstrumentations({ + // Configuration for specific instrumentations can be added here if needed + // Example: + // '@opentelemetry/instrumentation-http': { + // applyCustomAttributesOnSpan: (span, request, response) => { + // span.setAttribute('custom.attribute', 'value'); + // }, + // }, + }), + ], +}); + +// --- Start SDK and Graceful Shutdown --- +try { + sdk.start(); + diag.info('OpenTelemetry SDK started successfully for traces and metrics.'); +} catch (error) { + diag.error('Error starting OpenTelemetry SDK:', error); + process.exit(1); +} + +// Graceful shutdown +const shutdown = () => { + diag.info('Shutting down OpenTelemetry SDK...'); + sdk.shutdown() + .then(() => diag.info('OpenTelemetry SDK shut down successfully.')) + .catch(error => diag.error('Error shutting down OpenTelemetry SDK:', error)) + .finally(() => process.exit(0)); +}; + +process.on('SIGTERM', shutdown); +process.on('SIGINT', shutdown); \ No newline at end of file diff --git a/server/node-service/.gitignore b/server/node-service/.gitignore index 04cdacb56d..57c109e6c4 100644 --- a/server/node-service/.gitignore +++ b/server/node-service/.gitignore @@ -1,7 +1,7 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies -/node_modules +/node_modules/** /packages/*/node_modules /packages/*/dist /packages/*/tsdoc-metadata.json From c1df04c245d5c9963a53bdbd19542231f7f425c5 Mon Sep 17 00:00:00 2001 From: "Connell, Joseph" Date: Sat, 24 May 2025 20:45:20 -0700 Subject: [PATCH 3/4] node-service auto-instrumentation --- deploy/docker/Dockerfile | 11 +- deploy/docker/node-service/entrypoint.sh | 3 - deploy/docker/node-service/otel-config.js | 139 ---------------------- 3 files changed, 9 insertions(+), 144 deletions(-) delete mode 100644 deploy/docker/node-service/otel-config.js diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index 3eca75d472..d86c5e4443 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -78,10 +78,14 @@ WORKDIR /lowcoder/node-service/app/ RUN yarn --immutable RUN yarn build -# Copy startup script and OpenTelemetry config +# --- ADDED: Install OpenTelemetry dependencies for Node.js service --- +RUN yarn add \ + @opentelemetry/api \ + @opentelemetry/auto-instrumentations-node + +# Copy startup script COPY deploy/docker/node-service/entrypoint.sh /lowcoder/node-service/entrypoint.sh COPY deploy/docker/node-service/init.sh /lowcoder/node-service/init.sh -COPY deploy/docker/node-service/otel-config.js /lowcoder/node-service/otel-config.js RUN chmod +x /lowcoder/node-service/*.sh ## @@ -110,6 +114,9 @@ RUN apt-get update \ COPY --from=build-node-service /lowcoder/node-service /lowcoder/node-service +#Enable OpenTelemetry node agent +ENV NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register" + EXPOSE 6060 CMD [ "/bin/sh", "/lowcoder/node-service/entrypoint.sh" ] diff --git a/deploy/docker/node-service/entrypoint.sh b/deploy/docker/node-service/entrypoint.sh index bd2bafc13c..33308e4c7b 100755 --- a/deploy/docker/node-service/entrypoint.sh +++ b/deploy/docker/node-service/entrypoint.sh @@ -23,7 +23,4 @@ if [ "$(id -u)" -eq 0 ]; then fi echo -# Require OpenTelemetry configuration -export NODE_OPTIONS="-r ./otel-config.js" - exec $GOSU yarn start diff --git a/deploy/docker/node-service/otel-config.js b/deploy/docker/node-service/otel-config.js deleted file mode 100644 index 05b2128747..0000000000 --- a/deploy/docker/node-service/otel-config.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - * OpenTelemetry Configuration for Lowcoder Application - * - * This file sets up OpenTelemetry auto-instrumentation for traces and metrics. - * - * How to use: - * 1. Save this file as `otel-config.js` in your application's root directory. - * 2. Ensure you have the necessary OpenTelemetry packages installed: - * @opentelemetry/sdk-node - * @opentelemetry/api - * @opentelemetry/exporter-trace-otlp-http (or -grpc) - * @opentelemetry/exporter-metrics-otlp-http (or -grpc) - * @opentelemetry/resources - * @opentelemetry/semantic-conventions - * @opentelemetry/auto-instrumentations-node (CRITICAL for auto-instrumentation) - * - * Install @opentelemetry/auto-instrumentations-node if you haven't: - * `npm install @opentelemetry/auto-instrumentations-node` - * or - * `yarn add @opentelemetry/auto-instrumentations-node` - * - * 3. Start your application using the `-r` flag to preload this configuration: - * `export NODE_OPTIONS="-r ./otel-config.js"` - * - * Environment Variables for Configuration: - * - OTEL_EXPORTER_OTLP_ENDPOINT: Base URL for OTLP HTTP exporters (e.g., http://localhost:4318). - * If not set, defaults to http://localhost:4318. - * - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: Specific URL for OTLP HTTP traces exporter (e.g., http://localhost:4318/v1/traces). - * - OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: Specific URL for OTLP HTTP metrics exporter (e.g., http://localhost:4318/v1/metrics). - * - OTEL_SERVICE_NAME: Name of your service (e.g., 'node-service'). Defaults to 'unknown_service:nodejs'. - * - OTEL_LOG_LEVEL: Set OpenTelemetry diagnostic logging level (e.g., 'debug', 'info', 'warn', 'error'). - * - OTEL_EXPORTER_PROTOCOL: 'http/protobuf' (default) or 'grpc'. - */ - -const process = require('process'); -const { NodeSDK } = require('@opentelemetry/sdk-node'); -const { OTLPTraceExporter: OTLPTraceExporterHttp } = require('@opentelemetry/exporter-trace-otlp-http'); -const { OTLPTraceExporter: OTLPTraceExporterGrpc } = require('@opentelemetry/exporter-trace-otlp-grpc'); -const { OTLPMetricExporter: OTLPMetricExporterHttp } = require('@opentelemetry/exporter-metrics-otlp-http'); -const { OTLPMetricExporter: OTLPMetricExporterGrpc } = require('@opentelemetry/exporter-metrics-otlp-grpc'); -const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-node'); // Using BatchSpanProcessor for better performance -const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics'); -const { Resource } = require('@opentelemetry/resources'); -const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions'); -const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node'); -const { DiagConsoleLogger, DiagLogLevel, diag } = require('@opentelemetry/api'); - -// --- Configuration --- -const SERVICE_NAME = process.env.OTEL_SERVICE_NAME || 'lowcoder-node-service'; -const OTLP_EXPORTER_PROTOCOL = process.env.OTEL_EXPORTER_PROTOCOL || 'http/protobuf'; // 'http/protobuf' or 'grpc' - -const DEFAULT_OTLP_HTTP_ENDPOINT = 'http://localhost:4318'; -const DEFAULT_OTLP_GRPC_ENDPOINT = 'http://localhost:4317'; - -const OTLP_ENDPOINT = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || - (OTLP_EXPORTER_PROTOCOL === 'grpc' ? DEFAULT_OTLP_GRPC_ENDPOINT : DEFAULT_OTLP_HTTP_ENDPOINT); - -const TRACES_ENDPOINT = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || - (OTLP_EXPORTER_PROTOCOL === 'grpc' ? OTLP_ENDPOINT : `${OTLP_ENDPOINT}/v1/traces`); -const METRICS_ENDPOINT = process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT || - (OTLP_EXPORTER_PROTOCOL === 'grpc' ? OTLP_ENDPOINT : `${OTLP_ENDPOINT}/v1/metrics`); - -// Optional: Set OpenTelemetry diagnostic logging level -const otelLogLevel = process.env.OTEL_LOG_LEVEL?.toUpperCase(); -if (otelLogLevel && DiagLogLevel[otelLogLevel]) { - diag.setLogger(new DiagConsoleLogger(), DiagLogLevel[otelLogLevel]); -} else { - diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO); // Default to INFO -} - -diag.info(`OpenTelemetry SDK configured for service: ${SERVICE_NAME}`); -diag.info(`Using OTLP protocol: ${OTLP_EXPORTER_PROTOCOL}`); -diag.info(`Traces Exporter Endpoint: ${TRACES_ENDPOINT}`); -diag.info(`Metrics Exporter Endpoint: ${METRICS_ENDPOINT}`); - -// --- Resource Definition --- -const resource = Resource.default().merge( - new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: SERVICE_NAME, - // [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0', // Optional - }) -); - -// --- Exporter Configuration --- -let traceExporter; -let metricExporter; - -if (OTLP_EXPORTER_PROTOCOL === 'grpc') { - diag.info('Using gRPC Exporters'); - traceExporter = new OTLPTraceExporterGrpc({ url: TRACES_ENDPOINT }); - metricExporter = new OTLPMetricExporterGrpc({ url: METRICS_ENDPOINT }); -} else { - diag.info('Using HTTP/protobuf Exporters'); - traceExporter = new OTLPTraceExporterHttp({ url: TRACES_ENDPOINT }); - metricExporter = new OTLPMetricExporterHttp({ url: METRICS_ENDPOINT }); -} - -// --- SDK Initialization --- -const sdk = new NodeSDK({ - resource: resource, - traceExporter: traceExporter, - spanProcessor: new BatchSpanProcessor(traceExporter), // Recommended for most cases - metricReader: new PeriodicExportingMetricReader({ - exporter: metricExporter, - exportIntervalMillis: 10000, // Export metrics every 10 seconds - }), - instrumentations: [ - getNodeAutoInstrumentations({ - // Configuration for specific instrumentations can be added here if needed - // Example: - // '@opentelemetry/instrumentation-http': { - // applyCustomAttributesOnSpan: (span, request, response) => { - // span.setAttribute('custom.attribute', 'value'); - // }, - // }, - }), - ], -}); - -// --- Start SDK and Graceful Shutdown --- -try { - sdk.start(); - diag.info('OpenTelemetry SDK started successfully for traces and metrics.'); -} catch (error) { - diag.error('Error starting OpenTelemetry SDK:', error); - process.exit(1); -} - -// Graceful shutdown -const shutdown = () => { - diag.info('Shutting down OpenTelemetry SDK...'); - sdk.shutdown() - .then(() => diag.info('OpenTelemetry SDK shut down successfully.')) - .catch(error => diag.error('Error shutting down OpenTelemetry SDK:', error)) - .finally(() => process.exit(0)); -}; - -process.on('SIGTERM', shutdown); -process.on('SIGINT', shutdown); \ No newline at end of file From 6a25813bb3c716d9566c1cafb89cd6bd0cd48f61 Mon Sep 17 00:00:00 2001 From: "Connell, Joseph" Date: Sun, 25 May 2025 11:37:38 -0700 Subject: [PATCH 4/4] updated docker compose image to public image. --- deploy/docker/docker-compose-multi-otel.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/docker/docker-compose-multi-otel.yaml b/deploy/docker/docker-compose-multi-otel.yaml index c4b5191d53..7d4a91ae3c 100644 --- a/deploy/docker/docker-compose-multi-otel.yaml +++ b/deploy/docker/docker-compose-multi-otel.yaml @@ -45,7 +45,7 @@ services: ## Start Lowcoder backend services (api-service and node-service) ## lowcoder-api-service: - image: lowcoder-ce-api-service-jmc:latest + image: lowcoderorg/lowcoder-ce-api-service:latest container_name: lowcoder-api-service # Enabled ports to be able to access backend from host # ports: @@ -135,7 +135,7 @@ services: lowcoder-node-service: - image: lowcoder-ce-node-service-jmc:latest + image: lowcoderorg/lowcoder-ce-node-service:latest container_name: lowcoder-node-service # Enabled ports to be able to access backend from host # ports: @@ -176,7 +176,7 @@ services: ## Start Lowcoder web frontend ## lowcoder-frontend: - image: lowcoder-ce-frontend-jmc:latest + image: lowcoderorg/lowcoder-ce-frontend:latest container_name: lowcoder-frontend ports: - "3000:3000"