| name | datadog |
| description | [Applies to: **/*] Enforce Datadog best practices for structured logging, consistent tagging, metric governance, and CI/CD integration to ensure reliable and actionable observability across all services. |
| source | cursor_mdc |
Datadog Best Practices
This guide outlines our team's definitive standards for integrating Datadog into our applications and infrastructure. Adhering to these rules ensures consistent, high-quality observability data, enabling faster debugging, better monitoring, and unified insights across our stack.
1. Unified Service Tagging (UST) is Non-Negotiable
Every single piece of telemetry (metrics, logs, traces, events) must include the service, env, and version tags. This is fundamental for correlating data across Datadog products and achieving true end-to-end observability. Without these, your data is effectively siloed and useless for holistic analysis.
Configuration Management
Always set these as environment variables in your deployment pipeline. This ensures consistency and reduces boilerplate.
❌ BAD: Ad-hoc tagging or missing core tags
import datadog.dogstatsd as dogstatsd
import logging
dogstatsd.DogStatsd(host='localhost', port=8125).increment('my_app.requests.total')
logging.info("User logged in successfully", extra={"user_id": "123"})
✅ GOOD: Centralized environment variables and automatic tag injection
export DD_SERVICE="my-api-service"
export DD_ENV="production"
export DD_VERSION="1.0.0"
export DD_AGENT_HOST="datadog-agent.monitoring.svc.cluster.local"
import logging
from ddtrace import tracer, config
from ddtrace.contrib.logging.logging import DatadogLogHandler
import datadog.dogstatsd as dogstatsd
tracer.configure(
hostname=config.agent.hostname,
port=config.agent.port,
service=config.service,
env=config.env,
version=config.version
)
handler = DatadogLogHandler(host=config.agent.hostname, port=10518)
logging.basicConfig(level=logging.INFO, handlers=[handler])
logger = logging.getLogger(__name__)
statsd = dogstatsd.DogStatsd(host=config.agent.hostname, port=config.agent.port)
@tracer.wrap()
def process_request(request_id):
tracer.current_span().set_tag('request.id', request_id)
logger.info("Processing request", extra={"request_id": request_id, "user_agent": "Mozilla/5.0"})
statsd.increment('my_api.requests.processed', tags=[])
process_request()
2. Structured Logging is Mandatory
Always emit logs in a structured (JSON) format. This makes logs parseable, searchable, and correlatable in Datadog. Unstructured logs are a last resort for debugging and should never be used for production telemetry.
❌ BAD: Unstructured log messages
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info(f"User {user_id} failed to authenticate because of invalid credentials.")
✅ GOOD: Structured logging with context and automatic trace correlation
import logging
from ddtrace.contrib.logging.logging import DatadogLogHandler
from ddtrace import tracer, config
handler = DatadogLogHandler(host=config.agent.hostname, port=10518)
logging.basicConfig(level=logging.INFO, handlers=[handler])
logger = logging.getLogger(__name__)
@tracer.wrap()
def authenticate_user(user_id, password_hash):
if password_hash != "expected_hash":
logger.error(
"Authentication failed",
extra={
"event": "user.authentication_failed",
"user.id": user_id,
"reason": "invalid_credentials",
"http.status_code": 401
}
)
return False
logger.info(
"Authentication successful",
extra={
"event": "user.authentication_successful",
"user.id": user_id,
"http.status_code": 200
}
)
return True
authenticate_user("test_user", "wrong_password")
3. Metric Governance: Avoid Sprawl
Custom metrics are powerful but can quickly lead to "metric sprawl" and increased costs if not managed. Always define a clear naming convention and lifecycle for custom metrics.
Naming Convention
Use service_name.component.metric_name.unit (e.g., my_api.database.query_duration.seconds).
❌ BAD: Inconsistent or overly granular metric names
dogstatsd.DogStatsd(host='localhost', port=8125).increment('requests.total')
dogstatsd.DogStatsd(host='localhost', port=8125).gauge('db_latency', 150)
dogstatsd.DogStatsd(host='localhost', port=8125).increment(f'user.{user_id}.login_attempts')
✅ GOOD: Structured, consistent, and tagged metrics
import datadog.dogstatsd as dogstatsd
from ddtrace import config
statsd = dogstatsd.DogStatsd(host=config.agent.hostname, port=config.agent.port)
def record_api_request(endpoint, status_code):
statsd.increment('my_api.requests.total', tags=[f'endpoint:{endpoint}', f'status_code:{status_code}'])
statsd.histogram('my_api.request_duration_ms', 120, tags=[f'endpoint:{endpoint}'])
def record_login_attempt(user_id, success):
statsd.increment('my_api.auth.login_attempts', tags=[f'user_id:{user_id}', f'success:{success}'])
record_api_request("/users", 200)
record_login_attempt("user-456", True)
4. Tracing: Context Propagation
Always ensure trace context is propagated across service boundaries. This is critical for end-to-end distributed tracing. Datadog's APM libraries handle this automatically for most common frameworks, but be aware of custom integrations.
❌ BAD: Breaking trace context across service calls
import requests
from ddtrace import tracer
@tracer.wrap()
def call_service_b():
response = requests.get("http://service-b/data")
return response.json()
✅ GOOD: Automatic trace context propagation (requires ddtrace-run or explicit instrumentation)
import requests
from ddtrace import tracer
@tracer.wrap()
def call_service_b():
response = requests.get("http://service-b/data")
return response.json()
5. CI/CD Integration for Observability
Always integrate Datadog CI Visibility into your build and deployment pipelines. This links code changes directly to their operational impact, closing the feedback loop from code to production.
Key Practices:
- Store API Keys Securely: Use secrets management (e.g., Azure Key Vault, AWS Secrets Manager, HashiCorp Vault) for
DD_API_KEY and DD_APP_KEY.
- Tag Releases: Inject commit hashes, branch names, and ticket IDs as tags on deployments and CI Visibility data.
- Monitor Build Metrics: Track build duration, test failures, and deployment success rates.
❌ BAD: Disconnected CI/CD and observability
- script: |
python -m pytest
displayName: 'Run Tests'
✅ GOOD: Integrated CI Visibility and deployment tagging
variables:
DD_API_KEY: $(DD_API_KEY)
DD_APP_KEY: $(DD_APP_KEY)
DD_SITE: "datadoghq.com"
DD_ENV: "staging"
DD_SERVICE: "my-api-service"
DD_VERSION: "$(Build.BuildId)"
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.x'
addToPath: true
- script: |
pip install ddtrace pytest
displayName: 'Install Dependencies'
- script: |
# Run tests with Datadog CI Visibility
# DD_GIT_COMMIT_SHA, DD_GIT_REPOSITORY_URL, DD_GIT_BRANCH are often auto-detected by ddtrace-run.
# For manual tagging, you can set them explicitly.
ddtrace-run pytest --ddtrace
displayName: 'Run Tests with Datadog CI Visibility'
env:
DD_API_KEY: $(DD_API_KEY)
DD_APP_KEY: $(DD_APP_KEY)
DD_SITE: $(DD_SITE)
DD_ENV: $(DD_ENV)