diff --git a/examples/CLAUDE.md b/examples/CLAUDE.md new file mode 100644 index 00000000..e1a64bdb --- /dev/null +++ b/examples/CLAUDE.md @@ -0,0 +1,111 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a Docker Compose-based demonstration project for testing distributed tracing with Apache httpd-datadog module. The project consists of three main services: + +- **apache**: Reverse proxy with Datadog tracing module, proxies requests to the HTTP service +- **httpjs**: Simple Node.js HTTP server with Datadog APM tracing +- **datadog**: Datadog Agent for collecting traces and logs + +## Architecture + +``` +Client → apache (port 8888) → httpjs (port 8080) → Datadog Agent (port 8126) +``` + +The Apache service loads the `mod_datadog.so` to enable APM tracing for proxy requests. The Node.js HTTP service uses the dd-trace library to instrument HTTP handlers. + +## Prerequisites + +Before running any commands, ensure the Datadog API key is set. You can either: + +**Option 1: Export environment variable** +```bash +export DD_API_KEY= +``` + +**Option 2: Use envchain (recommended for security)** +```bash +# Store API key in keychain (run once) +envchain --set datadog DD_API_KEY + +# Use envchain when running docker-compose commands +envchain datadog docker-compose up -d +``` + +The runtime environment must be linux/amd64 for the Datadog Apache module to work properly. + +## Common Commands + +### Start the application +```bash +# With exported environment variable +docker-compose up -d + +# Or with envchain +envchain datadog docker-compose up -d +``` + +### Make a test request (generates traces) +```bash +curl localhost:8888 +``` + +### Load testing +```bash +make stress +``` +This uses vegeta to send 1000 req/s for 60 seconds to test the tracing under load. + +### View logs +```bash +docker-compose logs -f [service_name] +``` + +### Rebuild and restart services (required after config changes) +```bash +# With exported environment variable +docker-compose down && docker-compose build && docker-compose up -d + +# Or with envchain +envchain datadog docker-compose down && envchain datadog docker-compose build && envchain datadog docker-compose up -d +``` + +### Stop and clean up +```bash +docker-compose down +``` + +## Service Details + +### apache (Port 8888) +- Uses httpd:2.4 base image with mod_datadog module +- Dynamically downloads latest mod_datadog module from GitHub releases +- Proxies all requests to the `api` service +- **Enhanced logging**: Includes Datadog trace and span IDs for correlation +- Log format: `dd.trace_id="%{datadog_trace_id}e" dd.span_id="%{datadog_span_id}e"` +- Exposes Apache server-status endpoint on port 81 for Datadog monitoring +- Configuration: `apache/httpd.conf` + +### httpjs (Port 8080) +- Simple Node.js HTTP server with Datadog APM tracing via dd-trace +- Returns JSON response with service name and request headers +- Uses Alpine Linux base image with Node.js and npm +- Automatically instruments HTTP requests with dd-trace/init +- Listens on port 8080 and handles SIGTERM gracefully + +### datadog +- Official Datadog Agent 7 image +- Configured for APM, logs, and metrics collection +- Connects to containers via Docker socket for autodiscovery + +## File Structure + +- `docker-compose.yml`: Main orchestration configuration +- `apache/`: Apache configuration and Dockerfile +- `httpjs/`: Node.js HTTP service with Dockerfile and http.js server +- `Makefile`: Contains stress testing command +- `doc/`: Documentation and trace sample images \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..694b3492 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,68 @@ +# httpd-datadog Examples + +This directory contains a Docker Compose example demonstrating distributed tracing with the Apache httpd-datadog module. + +## Overview + +The example consists of three services: + +- **Apache HTTP Server** - Reverse proxy with mod_datadog module for tracing +- **Node.js HTTP Service** - Simple HTTP server with dd-trace instrumentation +- **Datadog Agent** - Collects and forwards traces to Datadog + +## Architecture + +``` +Client → Apache (port 8888) → Node.js (port 8080) → Datadog Agent (port 8126) +``` + +## Prerequisites + +Set your Datadog API key: +```bash +export DD_API_KEY= +``` + +## Quick Start + +1. Build and start services: +```bash +docker-compose up -d +``` + +2. Test the setup: +```bash +curl localhost:8888 +``` + +3. View logs: +```bash +docker-compose logs -f +``` + +4. Stop services: +```bash +docker-compose down +``` + +## What You'll See + +The Node.js service returns a JSON response showing the request headers, including Datadog tracing headers added by the Apache module: + +![Apache Tracing Example](img/apache-tracing-example.png) + +The above screenshot shows the distributed trace in Datadog's APM interface, demonstrating how requests flow from Apache through to the Node.js service with full trace correlation. + +```json +{ + "service": "http", + "headers": { + "x-datadog-trace-id": "...", + "x-datadog-parent-id": "...", + "traceparent": "...", + "tracestate": "..." + } +} +``` + +These headers demonstrate that distributed tracing is working correctly across the Apache proxy and Node.js backend. \ No newline at end of file diff --git a/examples/apache/Dockerfile b/examples/apache/Dockerfile new file mode 100644 index 00000000..c459e3ad --- /dev/null +++ b/examples/apache/Dockerfile @@ -0,0 +1,44 @@ +FROM debian:stable-slim + +# Install dependencies (only baseline system tools) +RUN apt-get update && \ + apt-get install -y apache2 curl unzip && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Enable required Apache modules +RUN a2enmod rewrite proxy proxy_http + +# Download and install mod_datadog directly +RUN cd /tmp && \ + # Get latest release info using curl and basic text processing + RELEASE_DATA=$(curl -s https://api.github.com/repos/DataDog/httpd-datadog/releases/latest) && \ + # Extract download URL for the zip file using grep and cut + DOWNLOAD_URL=$(echo "$RELEASE_DATA" | grep -o '"browser_download_url": *"[^"]*mod_datadog_artifact.zip"' | cut -d '"' -f 4) && \ + # Download and install + curl -Lf -o mod_datadog_artifact.zip "$DOWNLOAD_URL" && \ + unzip -j mod_datadog_artifact.zip -d /usr/lib/apache2/modules/ && \ + rm mod_datadog_artifact.zip + +# Enable datadog module +RUN echo "LoadModule datadog_module /usr/lib/apache2/modules/mod_datadog.so" > /etc/apache2/mods-available/datadog.load && \ + a2enmod datadog + +# Copy Apache configuration +COPY apache-config.conf /etc/apache2/sites-available/000-default.conf + +# Update ports configuration to listen on 8888 and 81 +RUN sed -i 's/Listen 80/Listen 8888\nListen 81/' /etc/apache2/ports.conf + +# Create app directory +RUN mkdir -p /app + +# Create startup script +RUN echo '#!/bin/bash\napachectl -D FOREGROUND' > /app/start.sh && \ + chmod +x /app/start.sh + +WORKDIR /app + +EXPOSE 8888 81 + +CMD ["./start.sh"] \ No newline at end of file diff --git a/examples/apache/apache-config.conf b/examples/apache/apache-config.conf new file mode 100644 index 00000000..b2ceff1d --- /dev/null +++ b/examples/apache/apache-config.conf @@ -0,0 +1,37 @@ + + ServerName localhost + + # Proxy configuration to the Node.js HTTP service + ProxyPreserveHost On + ProxyPass /healthcheck ! + ProxyPass / http://httpjs:8080/ + ProxyPassReverse / http://httpjs:8080/ + + # Create healthcheck endpoint + Alias /healthcheck /app/healthcheck.json + + # Log configuration with Datadog trace correlation + LogFormat "%h %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\" \"%{X-Forwarded-For}i\" dd.trace_id=\"%{Datadog-Trace-ID}e\" dd.span_id=\"%{Datadog-Span-ID}e\"" datadog_combined + CustomLog /proc/self/fd/1 datadog_combined + ErrorLog /proc/self/fd/2 + + # MIME types + AddType text/html .html + AddType text/plain .txt .log .conf + AddType application/json .json + + ErrorDocument 404 "Not Found" + + +# Extended status for more metrics (must be outside VirtualHost) +ExtendedStatus On + +# Additional VirtualHost for Apache status monitoring (port 81) + + ServerName _ + + + SetHandler server-status + Require all granted + + \ No newline at end of file diff --git a/examples/apache/httpd.conf b/examples/apache/httpd.conf new file mode 100644 index 00000000..5e12c8fe --- /dev/null +++ b/examples/apache/httpd.conf @@ -0,0 +1,81 @@ +# Load mod_datadog module for enabling Datadog APM Traces for proxy +LoadModule datadog_module modules/mod_datadog.so + +# Basic Apache configuration +ServerRoot "/usr/local/apache2" +Listen 80 +Listen 81 + +# Modules +LoadModule mpm_event_module modules/mod_mpm_event.so +LoadModule authz_core_module modules/mod_authz_core.so +LoadModule dir_module modules/mod_dir.so +LoadModule mime_module modules/mod_mime.so +LoadModule rewrite_module modules/mod_rewrite.so +LoadModule proxy_module modules/mod_proxy.so +LoadModule proxy_http_module modules/mod_proxy_http.so +LoadModule status_module modules/mod_status.so +LoadModule log_config_module modules/mod_log_config.so +LoadModule setenvif_module modules/mod_setenvif.so + +# Basic settings +ServerName localhost:80 +DirectoryIndex index.html + +# MIME types +TypesConfig conf/mime.types + +# Log configuration with Datadog trace correlation +# ref. https://docs.datadoghq.com/integrations/apache/?tab=host#log-collection +LogFormat "%h %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\" \"%{X-Forwarded-For}i\" dd.trace_id=\"%{datadog_trace_id}e\" dd.span_id=\"%{datadog_span_id}e\"" datadog_combined + +# Keep original format as backup +LogFormat "%h %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\" \"%{X-Forwarded-For}i\"" combined + +CustomLog "logs/access_log" datadog_combined +ErrorLog "logs/error_log" +LogLevel warn + +# Security + + AllowOverride none + Require all denied + + +# Document root +DocumentRoot "/usr/local/apache2/htdocs" + + AllowOverride None + Require all granted + + +# Main server - reverse proxy to API + + ServerName localhost + + # Enable proxy + ProxyPreserveHost On + ProxyPass / http://api:8080/ + ProxyPassReverse / http://api:8080/ + + # Add headers for tracing + ProxyPassReverse / http://api:8080/ + ProxyPreserveHost On + + +# Extended status for more metrics +ExtendedStatus On + +# Status server for Datadog monitoring + + ServerName _ + + # Disable access logging for status endpoint + CustomLog "logs/access_log" combined env=!dontlog + SetEnvIf Request_URI "^/server-status" dontlog + + + SetHandler server-status + Require all granted + + \ No newline at end of file diff --git a/examples/docker-compose.yml b/examples/docker-compose.yml new file mode 100644 index 00000000..a1fec0cd --- /dev/null +++ b/examples/docker-compose.yml @@ -0,0 +1,60 @@ +version: "3.7" + +services: + httpjs: + build: + context: ./httpjs + dockerfile: Dockerfile + container_name: sample-httpjs + labels: + com.datadoghq.ad.logs: '[{"source": "httpjs", "service": "sample-httpjs"}]' + ports: + - 8080:8080 + environment: + - DD_ENV=dev + - DD_HOSTNAME=local + - DD_AGENT_HOST=datadog + - DD_TRACE_AGENT_PORT=8126 + + apache: + build: + context: ./apache + dockerfile: Dockerfile + platform: linux/amd64 + container_name: sample-apache + depends_on: + - httpjs + image: sample-apache + labels: + com.datadoghq.tags.env: 'dev' + com.datadoghq.tags.service: 'sample-apache' + com.datadoghq.tags.version: '0.1.0' + com.datadoghq.ad.check_names: '["apache"]' + com.datadoghq.ad.init_configs: '[{}]' + com.datadoghq.ad.instances: '[{"apache_status_url": "http://%%host%%:81/server-status?auto"}]' + com.datadoghq.ad.logs: '[{"source": "apache", "service": "sample-apache"}]' + ports: + - "8888:8888" + - "81:81" + environment: + DD_AGENT_HOST: datadog + DD_TRACE_AGENT_PORT: 8126 + + datadog: + image: datadog/agent:7 + container_name: sample-ddagent + environment: + - DD_API_KEY + - DD_SITE=datadoghq.com + - DD_ENV=dev + - DD_HOSTNAME=local + - DD_LOGS_ENABLED=true + - DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL=true + - DD_APM_NON_LOCAL_TRAFFIC=true + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /var/lib/docker/containers:/var/lib/docker/containers:ro + - /proc/:/host/proc/:ro + - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro + ports: + - "8126:8126/tcp" diff --git a/examples/httpjs/Dockerfile b/examples/httpjs/Dockerfile new file mode 100644 index 00000000..123a6e96 --- /dev/null +++ b/examples/httpjs/Dockerfile @@ -0,0 +1,14 @@ +FROM alpine:3.19 + +# Note: WORKDIR must already be set before installing npm. +# If WORKDIR is not set, then npm will be installed at the container root, which +# then will cause `npm install` to fail later. +RUN mkdir /opt/app +WORKDIR /opt/app + +RUN apk update && apk add nodejs npm +RUN npm install dd-trace winston + +COPY ./http.js /opt/app/http.js + +CMD ["node", "http.js"] diff --git a/examples/httpjs/README.md b/examples/httpjs/README.md new file mode 100644 index 00000000..bd6ac078 --- /dev/null +++ b/examples/httpjs/README.md @@ -0,0 +1,2 @@ +This is the source code and build instructions for an HTTP server. It's the +"http" upstream service in [docker-compose.yml](../../docker-compose.yml). \ No newline at end of file diff --git a/examples/httpjs/http.js b/examples/httpjs/http.js new file mode 100644 index 00000000..3112f1b3 --- /dev/null +++ b/examples/httpjs/http.js @@ -0,0 +1,48 @@ +// This is an HTTP server that listens on port 8080 and responds to all +// requests with some text, including the request headers as JSON. + +require('dd-trace').init({ logInjection: true }); + +const http = require('http'); +const process = require('process'); +const { createLogger, format, transports } = require('winston'); + +const logger = createLogger({ + level: 'info', + format: format.json(), // JSON required for auto-injection + transports: [new transports.Console()] +}); + +// In order for the span(s) associated with an HTTP request to be considered +// finished, the body of the response corresponding to the request must have +// ended. +const ignoreRequestBody = request => { + request.on('data', () => {}); + request.on('end', () => {}); +} + +const requestListener = function (request, response) { + ignoreRequestBody(request); + const responseBody = JSON.stringify({ + "service": "http", + "headers": request.headers + }, null, 2); + + // Log with Winston for automatic trace injection + logger.info('HTTP request processed', { + method: request.method, + url: request.url, + userAgent: request.headers['user-agent'] + }); + + response.end(responseBody); +} + +console.log('http node.js web server is running'); +const server = http.createServer(requestListener); +server.listen(8080); + +process.on('SIGTERM', function () { + console.log('Received SIGTERM'); + server.close(function () { process.exit(0); }); +}); diff --git a/examples/img/apache-tracing-example.png b/examples/img/apache-tracing-example.png new file mode 100644 index 00000000..512d5fdb Binary files /dev/null and b/examples/img/apache-tracing-example.png differ