#!/bin/bash # ═══════════════════════════════════════════════════════════ # 🔧 OPHION Universal Instrumentation Script # Auto-instrument any containerized application # ═══════════════════════════════════════════════════════════ # # Usage: # ./instrument.sh [language] [ophion_server] # # Examples: # ./instrument.sh myapp # Auto-detect language # ./instrument.sh myapp nodejs # Explicit Node.js # ./instrument.sh myapp python http://ophion:4318 # # Supported languages: dotnet, nodejs, python, java, go, php # ═══════════════════════════════════════════════════════════ set -e # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color print_banner() { echo -e "${BLUE}" echo "╔═══════════════════════════════════════════════════════════╗" echo "║ 🐍 OPHION - Universal Instrumentation ║" echo "╚═══════════════════════════════════════════════════════════╝" echo -e "${NC}" } log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } # Configuration CONTAINER_NAME="${1:-}" LANGUAGE="${2:-auto}" OPHION_SERVER="${3:-http://ophion-otel-collector:4318}" OTEL_VOLUME="ophion-otel-agents" if [[ -z "$CONTAINER_NAME" ]]; then print_banner echo "Usage: $0 [language] [ophion_server]" echo "" echo "Arguments:" echo " container_name Name or ID of the Docker container" echo " language One of: dotnet, nodejs, python, java, go, php, auto (default)" echo " ophion_server OTLP endpoint (default: http://ophion-otel-collector:4318)" echo "" echo "Examples:" echo " $0 myapp" echo " $0 myapp nodejs" echo " $0 myapp python http://localhost:4318" exit 1 fi print_banner # Check if container exists if ! docker inspect "$CONTAINER_NAME" &>/dev/null; then log_error "Container '$CONTAINER_NAME' not found" exit 1 fi log_info "Container: $CONTAINER_NAME" # Detect language if auto detect_language() { local container="$1" log_info "Auto-detecting language..." # Check running processes local processes processes=$(docker exec "$container" ps aux 2>/dev/null || echo "") # Check for specific runtimes if echo "$processes" | grep -qE "dotnet|aspnet"; then echo "dotnet" elif echo "$processes" | grep -qE "node|npm|yarn"; then echo "nodejs" elif echo "$processes" | grep -qE "python|gunicorn|uvicorn|flask|django"; then echo "python" elif echo "$processes" | grep -qE "java|jvm"; then echo "java" elif docker exec "$container" test -f /usr/local/bin/php 2>/dev/null; then echo "php" else # Check for common files if docker exec "$container" test -f package.json 2>/dev/null; then echo "nodejs" elif docker exec "$container" test -f requirements.txt 2>/dev/null || \ docker exec "$container" test -f pyproject.toml 2>/dev/null; then echo "python" elif docker exec "$container" test -f pom.xml 2>/dev/null || \ docker exec "$container" test -f build.gradle 2>/dev/null; then echo "java" elif docker exec "$container" find / -name "*.csproj" 2>/dev/null | head -1 | grep -q .; then echo "dotnet" elif docker exec "$container" test -f composer.json 2>/dev/null; then echo "php" elif docker exec "$container" test -f go.mod 2>/dev/null; then echo "go" else echo "unknown" fi fi } if [[ "$LANGUAGE" == "auto" ]]; then LANGUAGE=$(detect_language "$CONTAINER_NAME") if [[ "$LANGUAGE" == "unknown" ]]; then log_error "Could not auto-detect language. Please specify: dotnet, nodejs, python, java, go, php" exit 1 fi log_success "Detected language: $LANGUAGE" fi # Ensure OTEL volume exists if ! docker volume inspect "$OTEL_VOLUME" &>/dev/null; then log_warn "OTEL agents volume not found. Creating..." docker run --rm -v "$OTEL_VOLUME":/otel ophion-instrumentation 2>/dev/null || { log_error "Failed to create OTEL agents volume. Run 'docker compose up instrumentation' first." exit 1 } fi # Get container image to determine if it's Alpine (musl) or glibc IS_ALPINE=false if docker exec "$CONTAINER_NAME" cat /etc/os-release 2>/dev/null | grep -qi alpine; then IS_ALPINE=true fi # Service name from container name SERVICE_NAME="${CONTAINER_NAME//-/_}" log_info "Language: $LANGUAGE" log_info "OTLP Endpoint: $OPHION_SERVER" log_info "Service Name: $SERVICE_NAME" # Common environment variables COMMON_ENV=( -e "OTEL_EXPORTER_OTLP_ENDPOINT=$OPHION_SERVER" -e "OTEL_SERVICE_NAME=$SERVICE_NAME" -e "OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.namespace=ophion" -e "OTEL_TRACES_EXPORTER=otlp" -e "OTEL_METRICS_EXPORTER=otlp" -e "OTEL_LOGS_EXPORTER=otlp" ) generate_docker_compose() { local lang="$1" local output_file="docker-compose.instrumented.yml" cat > "$output_file" << EOF # Auto-generated by OPHION instrument.sh # Add these settings to your docker-compose.yml version: '3.8' services: ${CONTAINER_NAME}: # Add these environment variables: environment: - OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER} - OTEL_SERVICE_NAME=${SERVICE_NAME} - OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production EOF case "$lang" in dotnet) local dotnet_path="/otel/dotnet" [[ "$IS_ALPINE" == true ]] && dotnet_path="/otel/dotnet-musl" cat >> "$output_file" << EOF - CORECLR_ENABLE_PROFILING=1 - CORECLR_PROFILER={918728DD-259F-4A6A-AC2B-B85E1B658571} - CORECLR_PROFILER_PATH=${dotnet_path}/linux-x64/OpenTelemetry.AutoInstrumentation.Native.so - DOTNET_ADDITIONAL_DEPS=${dotnet_path}/AdditionalDeps - DOTNET_SHARED_STORE=${dotnet_path}/store - DOTNET_STARTUP_HOOKS=${dotnet_path}/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll - OTEL_DOTNET_AUTO_HOME=${dotnet_path} volumes: - ophion-otel-agents:/otel:ro EOF ;; nodejs) cat >> "$output_file" << EOF - NODE_OPTIONS=--require /otel/nodejs/instrument.js volumes: - ophion-otel-agents:/otel:ro # Note: Run 'npm install' in /otel/nodejs first or copy package.json deps EOF ;; python) cat >> "$output_file" << EOF # For Python, modify your entrypoint: # command: opentelemetry-instrument python your-app.py # Or use PYTHONPATH volumes: - ophion-otel-agents:/otel:ro # Run: pip install opentelemetry-distro opentelemetry-exporter-otlp && opentelemetry-bootstrap -a install EOF ;; java) cat >> "$output_file" << EOF - JAVA_TOOL_OPTIONS=-javaagent:/otel/java/opentelemetry-javaagent.jar volumes: - ophion-otel-agents:/otel:ro EOF ;; php) cat >> "$output_file" << EOF # For PHP, add to your composer.json: # "require": { "open-telemetry/sdk": "^1.0", "open-telemetry/exporter-otlp": "^1.0" } # And include /otel/php/otel_init.php in your bootstrap volumes: - ophion-otel-agents:/otel:ro EOF ;; go) cat >> "$output_file" << EOF # For Go, instrumentation is compile-time. # See /otel/go/init.go.example for code to add to your application. # No runtime volumes needed. EOF ;; esac cat >> "$output_file" << EOF networks: default: external: true name: ophion-network volumes: ophion-otel-agents: external: true EOF log_success "Generated $output_file" } # Apply instrumentation based on language case "$LANGUAGE" in dotnet) log_info "Applying .NET auto-instrumentation..." DOTNET_PATH="/otel/dotnet" [[ "$IS_ALPINE" == true ]] && DOTNET_PATH="/otel/dotnet-musl" cat << EOF 📋 Add these environment variables to your container: docker run \\ -e CORECLR_ENABLE_PROFILING=1 \\ -e CORECLR_PROFILER={918728DD-259F-4A6A-AC2B-B85E1B658571} \\ -e CORECLR_PROFILER_PATH=${DOTNET_PATH}/linux-x64/OpenTelemetry.AutoInstrumentation.Native.so \\ -e DOTNET_ADDITIONAL_DEPS=${DOTNET_PATH}/AdditionalDeps \\ -e DOTNET_SHARED_STORE=${DOTNET_PATH}/store \\ -e DOTNET_STARTUP_HOOKS=${DOTNET_PATH}/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll \\ -e OTEL_DOTNET_AUTO_HOME=${DOTNET_PATH} \\ -e OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER} \\ -e OTEL_SERVICE_NAME=${SERVICE_NAME} \\ -v ${OTEL_VOLUME}:/otel:ro \\ --network ophion-network \\ your-image EOF ;; nodejs) log_info "Applying Node.js auto-instrumentation..." cat << EOF 📋 Add these to your container: # Option 1: NODE_OPTIONS (recommended) docker run \\ -e NODE_OPTIONS="--require /otel/nodejs/instrument.js" \\ -e OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER} \\ -e OTEL_SERVICE_NAME=${SERVICE_NAME} \\ -v ${OTEL_VOLUME}:/otel:ro \\ --network ophion-network \\ your-image # Option 2: Modify your package.json start script # "start": "node -r /otel/nodejs/instrument.js your-app.js" # Note: First install deps: cd /otel/nodejs && npm install EOF ;; python) log_info "Applying Python auto-instrumentation..." cat << EOF 📋 Add these to your container: # First, install packages in your Dockerfile: # RUN pip install opentelemetry-distro opentelemetry-exporter-otlp && \\ # opentelemetry-bootstrap -a install # Then run with: docker run \\ -e OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER} \\ -e OTEL_SERVICE_NAME=${SERVICE_NAME} \\ --network ophion-network \\ your-image \\ opentelemetry-instrument python your-app.py # Or use OTEL_PYTHON_CONFIGURATOR=true in your Dockerfile EOF ;; java) log_info "Applying Java auto-instrumentation..." cat << EOF 📋 Add these to your container: docker run \\ -e JAVA_TOOL_OPTIONS="-javaagent:/otel/java/opentelemetry-javaagent.jar" \\ -e OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER} \\ -e OTEL_SERVICE_NAME=${SERVICE_NAME} \\ -v ${OTEL_VOLUME}:/otel:ro \\ --network ophion-network \\ your-image EOF ;; go) log_info "Go instrumentation requires code changes..." cat << EOF 📋 Go requires compile-time instrumentation. Add this code: 1. Add dependencies: go get go.opentelemetry.io/otel go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp 2. See example code: /otel/go/init.go.example 3. Set environment: OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER} OTEL_SERVICE_NAME=${SERVICE_NAME} EOF ;; php) log_info "Applying PHP auto-instrumentation..." cat << EOF 📋 Add to your PHP application: 1. Add to composer.json: { "require": { "open-telemetry/sdk": "^1.0", "open-telemetry/exporter-otlp": "^1.0", "php-http/guzzle7-adapter": "^1.0" } } 2. Run: composer require open-telemetry/sdk open-telemetry/exporter-otlp 3. Include in bootstrap: require_once '/otel/php/otel_init.php'; 4. Set environment: OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER} OTEL_SERVICE_NAME=${SERVICE_NAME} EOF ;; *) log_error "Unsupported language: $LANGUAGE" exit 1 ;; esac # Generate docker-compose snippet generate_docker_compose "$LANGUAGE" echo "" log_success "Instrumentation configuration generated!" log_info "See docker-compose.instrumented.yml for complete configuration" echo "" echo -e "${GREEN}Next steps:${NC}" echo "1. Apply the environment variables to your container" echo "2. Ensure container is on the 'ophion-network' network" echo "3. Restart your container" echo "4. Check traces at http://localhost:3000 (Ophion Dashboard)"