datadog — Cursor rule: unified tags, logs and metrics
A Cursor rule that sets Datadog standards: required service, env and version tags, structured logs, and a naming scheme for custom metrics.
.cursor/rules/datadog.mdcWhat datadog does
The rule spells out how the team wires Datadog into applications and infrastructure. Every piece of telemetry — metric, log, trace, event — has to carry service, env and version tags. Without them data from different Datadog products cannot be correlated, and incident debugging turns into guesswork.
Tags go in as deployment environment variables: DD_SERVICE, DD_ENV, DD_VERSION, DD_AGENT_HOST. The Python examples use ddtrace with DatadogLogHandler, which injects service, env, version and trace identifiers on its own. Logs are emitted as structured records rather than formatted strings. Custom metrics follow service_name.component.metric_name.unit, and anything variable like user_id belongs in tags so you do not create a new metric per value.
Drop the rule into .cursor/rules/datadog.mdc and it applies across the project, since the globs pattern is **/*. The examples are Python with ddtrace, so on another stack you port the snippets yourself. The source text cuts off partway through the section on propagating trace context across service boundaries.
What it helps with
- add service, env and version tags to every metric, log and trace
- emit structured logs with automatic trace_id injection
- move variable values like user_id out of metric names into tags
- set one naming scheme for custom metrics
- wire DatadogLogHandler and DogStatsd into a Python app
Example prompts for the agent
- add service, env and version tags to the metrics in this module
- rewrite this log line as a structured record with trace_id
- check the custom metric names against service_name.component.metric_name.unit
How to install datadog
Manually: put the files in place
.cursor/rules/datadog.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/datadog.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/datadog.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/datadog.mdManually: put the files in place
.clinerules/datadog.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about datadog
How do I install the datadog rule in Cursor?
Create .cursor/rules/datadog.mdc in your project root and paste the rule contents into it. Cursor then applies it to every file, because the rule sets globs to **/*.
What does the datadog rule do?
It sets Datadog conventions: mandatory service, env and version tags, structured logging, a metric naming scheme, and trace context propagation between services.
What do I need for the rule to work?
The examples are Python and rely on ddtrace with DatadogLogHandler and dogstatsd. On another stack you rewrite the snippets for your own libraries.
Source text
datadog.mdc · CC0-1.0
---
description: Enforce Datadog best practices for structured logging, consistent tagging, metric governance, and CI/CD integration to ensure reliable and actionable observability across all services.
globs: **/*
---
# 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
```python
# Inconsistent or incomplete tagging
import datadog.dogstatsd as dogstatsd
import logging
# Metric without service/env/version
dogstatsd.DogStatsd(host='localhost', port=8125).increment('my_app.requests.total')
# Log without service/env/version context
logging.info("User logged in successfully", extra={"user_id": "123"})
```
✅ GOOD: Centralized environment variables and automatic tag injection
```bash
# Set these in your deployment environment (e.g., Kubernetes, Docker, CI/CD)
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" # Or your specific agent host
```
```python
# Python example using ddtrace and standard logging
import logging
from ddtrace import tracer, config
from ddtrace.contrib.logging.logging import DatadogLogHandler
import datadog.dogstatsd as dogstatsd
# Configure ddtrace to pick up env vars automatically.
# Ensure ddtrace is initialized early in your application lifecycle.
# For auto-instrumentation, run your app with `ddtrace-run python your_app.py`.
# If not using ddtrace-run, explicitly configure:
tracer.configure(
hostname=config.agent.hostname,
port=config.agent.port,
service=config.service, # Picks up DD_SERVICE env var
env=config.env, # Picks up DD_ENV env var
version=config.version # Picks up DD_VERSION env var
)
# Configure logging to send to Datadog agent and inject trace IDs
# DatadogLogHandler automatically adds service, env, version, trace_id, span_id
handler = DatadogLogHandler(host=config.agent.hostname, port=10518) # Default log intake port
logging.basicConfig(level=logging.INFO, handlers=[handler])
logger = logging.getLogger(__name__)
# Initialize DogStatsd client once, using agent host from ddtrace config
statsd = dogstatsd.DogStatsd(host=config.agent.hostname, port=config.agent.port)
@tracer.wrap()
def process_request(request_id):
# Metrics automatically inherit service/env/version from ddtrace config when using ddtrace-run
# Or ensure you pass them as tags if not using auto-instrumentation for metrics.
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=[f'request_id:{request_id}'])
# Example usage
process_request("req-abc-123")
```
## 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
```python
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
```python
import logging
from ddtrace.contrib.logging.logging import DatadogLogHandler
from ddtrace import tracer, config
# Ensure ddtrace is configured as above
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":
# Log structured data; trace_id and span_id are injected automatically by DatadogLogHandler
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
```python
# Too generic, hard to filter
dogstatsd.DogStatsd(host='localhost', port=8125).increment('requests.total')
dogstatsd.DogStatsd(host='localhost', port=8125).gauge('db_latency', 150)
# Overly specific, creates too many unique metrics
dogstatsd.DogStatsd(host='localhost', port=8125).increment(f'user.{user_id}.login_attempts')
```
✅ GOOD: Structured, consistent, and tagged metrics
```python
import datadog.dogstatsd as dogstatsd
from ddtrace import config
# Initialize DogStatsd client once, using agent host from ddtrace config
statsd = dogstatsd.DogStatsd(host=config.agent.hostname, port=config.agent.port)
def record_api_request(endpoint, status_code):
# Tags are crucial for slicing and dicing data without creating new metrics
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}'])
# Record a user login attempt, using tags for user_id instead of embedding in metric name
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
```python
# Service A
import requests
from ddtrace import tracer
@tracer.wrap()
def call_service_b():
# No trace headers propagated
response = requests.get("http://service-b/data")
return response.json()
```
✅ GOOD: Automatic trace context propagation (requires `ddtrace-run` or explicit instrumentation)
```python
# Service A (using requests with ddtrace auto-instrumentation)
import requests
from ddtrace import tracer
# Run your application with `ddtrace-run python your_app.py`
# Or explicitly patch requests:
# from ddtrace import patch; patch(requests=True)
@tracer.wrap()
def call_service_b():
# ddtrace automatically injects trace headers into outgoing requests
response = requests.get("http://service-b/data")
return response.json()
# Service B (with ddtrace auto-instrumentation)
# ddtrace automatically extracts trace headers from incoming requests
# and continues the trace.
```
## 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
```yaml
# azure-pipelines.yml (no Datadog integration)
- script: |
python -m pytest
displayName: 'Run Tests'
```
✅ GOOD: Integrated CI Visibility and deployment tagging
```yaml
# azure-pipelines.yml (example for Python, adapt for your language/framework)
variables:
DD_API_KEY: $(DD_API_KEY) # Stored securely in Key Vault
DD_APP_KEY: $(DD_APP_KEY) # Stored securely in Key Vault
DD_SITE: "datadoghq.com" # Or your specific Datadog site
DD_ENV: "staging" # Or production, based on environment
DD_SERVICE: "my-api-service"
DD_VERSION: "$(Build.BuildId)" # Use build ID or commit hash for version
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)
DD_SERVICE: $(DD_SERVICE)
DD_VERSION: $(DD_VERSION)
DD_CIVISIBILITY_ENABLED: "true"
DD_TAGS: "team:backend,project:my-app,build_id:$(Build.BuildId),commit:$(Build.SourceVersion)"
- script: |
# Example: Notifying Datadog of a deployment event
# Using curl for simplicity; in real-world use a dedicated Datadog API client or integration.
curl -X POST -H "Content-Type: application/json" \
-H "DD-API-KEY: $(DD_API_KEY)" \
-H "DD-APPLICATION-KEY: $(DD_APP_KEY)" \
"https://api.$(DD_SITE)/api/v1/events" \
-d '{
"title": "Deployment to $(DD_ENV) for $(DD_SERVICE)",
"text": "Version $(DD_VERSION) deployed by $(Build.RequestedFor)",
"tags": ["deployment", "env:$(DD_ENV)", "service:$(DD_SERVICE)", "version:$(DD_VERSION)", "commit:$(Build
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc