Skip to main content

Development Environment Configuration

Overview

This document describes the complete development environment required for developing and testing sovdev-logger implementations. The environment consists of two main components:

  1. DevContainer Toolbox - Provides all programming language runtimes
  2. Local Kubernetes Cluster - Runs the observability stack (Loki, Prometheus, Tempo, Grafana)

Both components are required for developing and testing any language implementation.


Architecture Diagram

This diagram shows the complete development environment architecture and how components interact:

┌─────────────────────────────────────────────────────────────────────────┐
│ HOST MACHINE (Mac/Windows/Linux) │
│ │
│ Human developers (optional): │
│ • File editing with VSCode │
│ • Work inside DevContainer for full toolchain access │
│ │
│ Project Files: /Users/.../sovdev-logger/ │
│ ↕ [bind mount - bidirectional, real-time sync] │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ DEVCONTAINER (Docker Container) │ │
│ │ │ │
│ │ Workspace: /workspace/ (same files as host via bind mount) │ │
│ │ │ │
│ │ 🤖 LLM Execution Context (Claude Code runs here): │ │
│ │ • File editing (Read/Edit/Write at /workspace/) │ │
│ │ • Command execution (direct) │ │
│ │ │ │
│ │ Code executes here: │ │
│ │ • Language runtimes (Node.js ✅, Python ✅, Go*, Rust*, etc.) │ │
│ │ • Test programs run │ │
│ │ • Validation tools run │ │
│ │ • OTLP export originates FROM here │ │
│ │ │ │
│ │ Your Test Program │ │
│ │ └─ sovdev_log() ───┐ │ │
│ │ │ │ │
│ │ ↓ │ │
│ │ ┌──────────────────┐ │ │
│ │ │ OpenTelemetry SDK│ │ │
│ │ │ OTLP Exporter │ │ │
│ │ └────────┬─────────┘ │ │
│ │ │ │ │
│ │ │ HTTP POST with header: │ │
│ │ │ Host: otel.localhost │ │
│ │ │ │ │
│ └───────────────────────┼───────────────────────────────────────────┘ │
│ │ │
└──────────────────────────┼──────────────────────────────────────────────┘

│ http://host.docker.internal/v1/logs
│ http://host.docker.internal/v1/metrics
│ http://host.docker.internal/v1/traces
│ Header: Host=otel.localhost ⚠️ REQUIRED


┌─────────────────────────────────────────────────────────────────────────┐
│ KUBERNETES CLUSTER (Local K3s via Rancher Desktop) │
│ │
│ Traefik Ingress (Port 80) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Routes based on Host header: │ │
│ │ │ │
│ │ Host: otel.localhost → OTLP Collector (port 4318) │ │
│ │ Host: grafana.localhost → Grafana (port 80) │ │
│ │ Host: loki.localhost → Loki (port 80) [optional] │ │
│ │ (No Host header) → 404 Not Found ❌ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ OTLP Collector (otel-collector-opentelemetry-collector) │ │
│ │ │ │
│ │ Receives: Logs, Metrics, Traces via OTLP/HTTP │ │
│ │ Exports to: Loki, Prometheus, Tempo │ │
│ └──────────┬──────────────────┬──────────────────┬────────────────┘ │
│ │ │ │ │
│ ┌──────┘ │ └──────┐ │
│ ↓ ↓ ↓ │
│ ┌────────┐ ┌──────────────┐ ┌─────────┐ │
│ │ LOKI │ │ PROMETHEUS │ │ TEMPO │ │
│ │ │ │ │ │ │ │
│ │ Stores │ │ Stores │ │ Stores │ │
│ │ Logs │ │ Metrics │ │ Traces │ │
│ └────┬───┘ └──────┬───────┘ └────┬────┘ │
│ │ │ │ │
│ │ │ │ │
│ └───────────────────────┼─────────────────────────┘ │
│ ↓ │
│ ┌─────────────┐ │
│ │ GRAFANA │ │
│ │ │ │
│ │ Dashboards │ │
│ │ Queries all │ │
│ │ 3 backends │ │
│ └─────────────┘ │
│ │
│ Access from browser: http://grafana.localhost │
│ │
└─────────────────────────────────────────────────────────────────────────┘

Validation: See tools/validation/uis/README.md for the complete tool reference, and 09-development-loop.md for the workflow.

Key Architecture:

  1. Files: Host project directory bind-mounted to /workspace/ in container
  2. Execution: Code runs inside DevContainer at /workspace/
  3. OTLP: DevContainer → http://host.docker.internal/v1/{logs,metrics,traces} with Host: otel.localhost header → Traefik → OTLP Collector
  4. Storage: OTLP Collector → Loki (logs), Prometheus (metrics), Tempo (traces)
  5. Visualization: Grafana queries all backends at http://grafana.localhost

Traefik requires Host header for routing - without it, requests return 404.


Component 1: DevContainer Toolbox

Purpose

The DevContainer provides a standardized development environment with all programming language runtimes pre-installed. This eliminates "works on my machine" problems and ensures consistent behavior across Mac, Windows, and Linux.

Architecture

Host Machine (Mac/Windows/Linux)
├── Project Files (editable from host)
└── DevContainer (running in Docker)
├── All Language Runtimes (Node.js, Python, Go, Java, PHP, C#, Rust)
├── Development Tools (git, curl, wget, etc.)
└── Workspace Mount: /workspace → Host Project Root

Key Specifications

PropertyValueNotes
Container Namedevcontainer-toolboxFixed name, always use this
Base ImageDebian 12 (bookworm)Stable, well-supported
Uservscode (UID 1000)Non-root user
Workspace Mount/workspace → Host project rootBidirectional read-write; changes on host instantly visible in container and vice versa
Network ModeBridge with host gatewayCan access host services via host.docker.internal

Workspace Mount

Host project directory is bind-mounted to /workspace/ in container:

  • Changes in container appear on host instantly (same filesystem, not a copy)
  • Use /workspace/ prefix for all file paths inside container
  • Example: /workspace/typescript/src/logger.ts maps to <project-root>/typescript/src/logger.ts on host

Language Runtimes

Pre-installed: Node.js, Python, PowerShell

Other languages: Install from /workspace/.devcontainer/additions/install-dev-*.sh

Each installation script has metadata at the top:

SCRIPT_NAME="C# Development Tools"
SCRIPT_DESCRIPTION="Complete .NET 8.0 development environment..."
CHECK_INSTALLED_COMMAND="command -v dotnet >/dev/null 2>&1"

To install a language:

# List available installers
ls /workspace/.devcontainer/additions/install-dev-*.sh

# Check if already installed (example: C#)
dotnet --version

# Install if not found
/workspace/.devcontainer/additions/install-dev-csharp.sh

Command Execution

All commands execute at /workspace/ inside the DevContainer. Validation tools are in /workspace/tools/validation/uis/ (local UIS backend) and /workspace/tools/validation/grafana-cloud/ (Grafana Cloud backend).

Network Access

From DevContainer to host services:

Use host.docker.internal DNS name to access services on the host machine (Kubernetes cluster):

# OTLP endpoints
http://host.docker.internal/v1/logs
http://host.docker.internal/v1/metrics
http://host.docker.internal/v1/traces

# All require: Host: otel.localhost header

Note: OTLP endpoint details covered in Component 2 section below.


Component 2: Local Kubernetes Cluster

Purpose

Runs the observability stack (Loki, Prometheus, Tempo, Grafana) that receives and stores telemetry during testing.

OTLP Endpoints (For Implementation)

Send telemetry to:

Endpoint: http://host.docker.internal/v1/{logs,metrics,traces}
Required Header: Host: otel.localhost
Protocol: HTTP/Protobuf

Environment variable:

OTEL_EXPORTER_OTLP_HEADERS=Host=otel.localhost

⚠️ CRITICAL: The Host: otel.localhost header is required for Traefik routing. Without it, requests fail with 404 errors.

Format note: OTEL_EXPORTER_OTLP_HEADERS is a standard OpenTelemetry env var — comma-separated key=value pairs (e.g. Host=otel.localhost,Authorization=Basic ...), not JSON. The OTel SDK reads this env var natively; a JSON value collides with that native parsing (see INVESTIGATE-otlp-headers-standard-compliance.md).

Troubleshooting: Some HTTP clients (e.g., Go) override custom Host headers. See task-06-implement-otlp.md subsection 6.12 for language-specific workarounds.

Validation

Use validation tools instead of direct queries:

cd /workspace/tools/validation/uis

# Query individual backends (add --compare-with LOGFILE for exact-match validation)
./query-loki.sh <service-name>
./query-prometheus.sh <service-name>
./query-tempo.sh <service-name>

# Cross-language conformance (the completion gate)
./compare-with-master.sh <language>

Complete tool documentation: See tools/validation/uis/README.md

Visualization

View results in Grafana:

  • URL: http://grafana.localhost
  • Credentials: admin/admin
  • Dashboards: Pre-configured for sovdev-logger

For troubleshooting: See Testing against UIS → Troubleshooting section


Environment Variables

For Application Code (Inside DevContainer)

Required for OTLP Export:

# OTLP Endpoints (from container to host)
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://host.docker.internal/v1/logs
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://host.docker.internal/v1/metrics
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://host.docker.internal/v1/traces

# OTLP Headers (Traefik routing) — key=value, not JSON (see above)
export OTEL_EXPORTER_OTLP_HEADERS=Host=otel.localhost

Service Identification:

# Service name (OpenTelemetry standard - appears in Grafana)
export OTEL_SERVICE_NAME="sovdev-test-company-lookup-typescript"

# Service version (optional)
export SERVICE_VERSION="1.0.0"

Logging Configuration:

# Enable console logging
export LOG_TO_CONSOLE=true

# Enable file logging
export LOG_TO_FILE=true
export LOG_FILE_PATH=./logs/dev.log
export ERROR_LOG_PATH=./logs/error.log

# Environment mode
export NODE_ENV=development # or production

File Rotation Configuration:

All implementations MUST implement log rotation to prevent disk space exhaustion:

Log TypeMax SizeMax FilesTotal Disk Usage
Main log50 MB5 files~250 MB max
Error log10 MB3 files~30 MB max
Total--~280 MB max

Implementation Requirements:

  • MUST rotate log files when size limit is reached
  • MUST keep only the specified number of rotated files
  • MUST delete oldest files when max files limit is reached
  • SHOULD use platform-appropriate file rotation libraries

Language-Specific Implementations:

TypeScript/JavaScript (Winston):

new winston.transports.File({
filename: logFilePath,
maxsize: 50 * 1024 * 1024, // 50MB
maxFiles: 5, // Keep 5 files
tailable: true // Use rotating file names
})

Python (logging.handlers.RotatingFileHandler):

from logging.handlers import RotatingFileHandler

handler = RotatingFileHandler(
filename='logs/dev.log',
maxBytes=50 * 1024 * 1024, # 50MB
backupCount=5 # Keep 5 backups
)

Go (lumberjack):

import "gopkg.in/natefinch/lumberjack.v2"

&lumberjack.Logger{
Filename: "logs/dev.log",
MaxSize: 50, // MB
MaxBackups: 5, // files
MaxAge: 0, // days (0 = no age limit)
}

Java (Log4j2 RollingFileAppender):

<RollingFile name="file" fileName="logs/dev.log"
filePattern="logs/dev-%i.log.gz">
<Policies>
<SizeBasedTriggeringPolicy size="50MB"/>
</Policies>
<DefaultRolloverStrategy max="5"/>
</RollingFile>

C# (Serilog FileSizeLimitBytes):

Log.Logger = new LoggerConfiguration()
.WriteTo.File(
"logs/dev.log",
fileSizeLimitBytes: 50 * 1024 * 1024, // 50MB
rollOnFileSizeLimit: true,
retainedFileCountLimit: 5
)
.CreateLogger();

PHP (Monolog RotatingFileHandler):

use Monolog\Handler\RotatingFileHandler;

$handler = new RotatingFileHandler(
'logs/dev.log',
5, // maxFiles
Logger::INFO,
true, // bubble
null, // filePermission
false // useLocking
);
$handler->setFilenameFormat('{filename}-{date}', 'Y-m-d');

Rust (tracing-appender):

use tracing_appender::rolling::{RollingFileAppender, Rotation};

let file_appender = RollingFileAppender::new(
Rotation::NEVER, // Rotation based on size, not time
"logs",
"dev.log"
);
// Note: Rust ecosystem may require custom size-based rotation

Example .env File (TypeScript)

# Service Configuration
OTEL_SERVICE_NAME=sovdev-test-company-lookup-typescript
SERVICE_VERSION=1.0.0

# OTLP Configuration (DevContainer → Host → Kubernetes)
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://host.docker.internal/v1/logs
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://host.docker.internal/v1/metrics
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://host.docker.internal/v1/traces
OTEL_EXPORTER_OTLP_HEADERS=Host=otel.localhost

# Logging Configuration
LOG_TO_CONSOLE=true
LOG_TO_FILE=true
LOG_FILE_PATH=./logs/dev.log
NODE_ENV=development

Example .env File (Python)

# Service Configuration
OTEL_SERVICE_NAME=sovdev-test-company-lookup-python
SERVICE_VERSION=1.0.0

# OTLP Configuration
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://host.docker.internal/v1/logs
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://host.docker.internal/v1/metrics
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://host.docker.internal/v1/traces
OTEL_EXPORTER_OTLP_HEADERS=Host=otel.localhost

# Logging Configuration
LOG_TO_CONSOLE=true
LOG_TO_FILE=true
LOG_FILE_PATH=./logs/dev.log

Troubleshooting

DevContainer Issues

Container not running:

# Check Docker is running
docker ps

# Rebuild container
# In VSCode: Cmd/Ctrl+Shift+P → "Dev Containers: Rebuild Container"

Cannot execute commands:

pwd # Should show: /workspace
ls -la /workspace # Should show project files

Kubernetes Cluster Issues

Pods not running:

# Check pod status
kubectl get pods -n monitoring

# Check pod logs
kubectl logs -n monitoring <pod-name>

# Describe pod for events
kubectl describe pod -n monitoring <pod-name>

Services not accessible:

# Check services
kubectl get svc -n monitoring

# Check ingress
kubectl get ingressroute -A

OTLP connection fails:

# Test OTLP collector from inside cluster
kubectl run curl-otel-test --image=curlimages/curl --rm -i --restart=Never -n monitoring -- \
curl -v http://otel-collector-opentelemetry-collector.monitoring.svc.cluster.local:13133/

# Check collector logs
kubectl logs -n monitoring -l app.kubernetes.io/name=opentelemetry-collector --tail=50

Grafana not accessible:

# Check Grafana pod
kubectl get pods -n monitoring -l app.kubernetes.io/name=grafana

# Port-forward as alternative
kubectl port-forward -n monitoring svc/grafana 3000:80
open http://localhost:3000

Network Issues

DevContainer cannot reach host:

curl -v http://host.docker.internal/ # Should return Traefik response

Logs not appearing in Loki:

  1. Check OTLP collector is receiving data: kubectl logs -n monitoring -l app.kubernetes.io/name=opentelemetry-collector
  2. Verify OTLP endpoint in application: echo $OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
  3. Check OTLP headers: echo $OTEL_EXPORTER_OTLP_HEADERS
  4. Verify application calls sovdev_shutdown() before exit (not sovdev_flush(), which never terminates the SDK)

Summary

Complete Environment Components

  1. DevContainer - All language runtimes (Node.js, Python, Go, Java, PHP, C#, Rust)
  2. Kubernetes Cluster - Observability stack (Loki, Prometheus, Tempo, Grafana)
  3. Traefik Ingress - Routes traffic to services via *.localhost domains
  4. OTLP Collector - Receives telemetry from applications

Key Connections

Application (DevContainer)
→ OTLP over HTTP (host.docker.internal)
→ Traefik Ingress (127.0.0.1:80)
→ OTLP Collector (Kubernetes)
→ Loki (logs) + Prometheus (metrics) + Tempo (traces)
→ Grafana (visualization)

Development Loop

For complete development workflow documentation, see 09-development-loop.md.

Quick Reference:

  1. Edit code on host (fast file operations)
  2. Run test in DevContainer (consistent runtimes)
  3. Validate log files FIRST ⚡ (instant, local, catches 90% of issues)
  4. Validate OTLP backends SECOND 🔄 (after log files pass, requires wait)

Key Principle: Always validate log files before checking OTLP backends - this provides instant feedback and catches most issues without waiting for infrastructure.

This environment ensures consistent behavior across all developers and reliable testing of all language implementations.


Document Status: ✅ v1.0.0 COMPLETE Last Updated: 2025-10-27 Part of: sovdev-logger specification v1.1.0