# Datadog Client

Query metrics, manage monitors, and interact with Datadog's monitoring and observability platform.

## Methods

| Method                                   | Description                                         |
| ---------------------------------------- | --------------------------------------------------- |
| `apiRequest(options, schema, metadata?)` | Make any Datadog API request with schema validation |

## Usage

### Query Metrics

```typescript
import { api, z, datadog } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_DATADOG = "a1b2c3d4-5678-90ab-cdef-datadog00001";

const MetricSeriesSchema = z.object({
  metric: z.string(),
  pointlist: z.array(z.tuple([z.number(), z.number().nullable()])),
  scope: z.string(),
  expression: z.string().optional(),
});

const QueryMetricsResponseSchema = z.object({
  status: z.string(),
  res_type: z.string(),
  from_date: z.number(),
  to_date: z.number(),
  series: z.array(MetricSeriesSchema),
  query: z.string(),
});

export default api({
  name: "DatadogExample",
  integrations: {
    datadog: datadog(PROD_DATADOG),
  },
  input: z.object({
    query: z.string(),
    fromSeconds: z.number(),
    toSeconds: z.number(),
  }),
  output: z.object({
    series: z.array(
      z.object({
        metric: z.string(),
        points: z.array(
          z.object({ timestamp: z.number(), value: z.number().nullable() }),
        ),
      }),
    ),
  }),
  async run(ctx, { query, fromSeconds, toSeconds }) {
    const result = await ctx.integrations.datadog.apiRequest(
      {
        method: "GET",
        path: "/api/v1/query",
        params: {
          query: query,
          from: fromSeconds,
          to: toSeconds,
        },
      },
      { response: QueryMetricsResponseSchema },
    );

    return {
      series: result.series.map((s) => ({
        metric: s.metric,
        points: s.pointlist.map(([timestamp, value]) => ({
          timestamp,
          value,
        })),
      })),
    };
  },
});
```

### List Monitors

```typescript
const MonitorSchema = z.object({
  id: z.number(),
  name: z.string(),
  type: z.string(),
  query: z.string(),
  message: z.string(),
  tags: z.array(z.string()),
  overall_state: z.string(), // OK, Alert, Warn, No Data
  created: z.string(),
  modified: z.string(),
  options: z
    .object({
      thresholds: z
        .object({
          critical: z.number().optional(),
          warning: z.number().optional(),
        })
        .optional(),
    })
    .optional(),
});

const result = await ctx.integrations.datadog.apiRequest(
  {
    method: "GET",
    path: "/api/v1/monitor",
    params: {
      tags: "env:production",
      page_size: 50,
    },
  },
  { response: z.array(MonitorSchema) },
);

result.forEach((monitor) => {
  console.log(`${monitor.name}: ${monitor.overall_state}`);
});
```

### Create a Monitor

```typescript
const monitor = await ctx.integrations.datadog.apiRequest(
  {
    method: "POST",
    path: "/api/v1/monitor",
    body: {
      name: "High CPU Usage Alert",
      type: "metric alert",
      query: "avg(last_5m):avg:system.cpu.user{env:production} > 90",
      message: "CPU usage is above 90% on {{host.name}}. @slack-alerts",
      tags: ["env:production", "team:platform"],
      options: {
        thresholds: {
          critical: 90,
          warning: 75,
        },
        notify_no_data: true,
        no_data_timeframe: 10,
        renotify_interval: 60,
      },
    },
  },
  { response: MonitorSchema },
);

console.log(`Created monitor: ${monitor.id}`);
```

### Send Custom Events

```typescript
const EventResponseSchema = z.object({
  status: z.string(),
  event: z.object({
    id: z.number(),
    url: z.string(),
  }),
});

const result = await ctx.integrations.datadog.apiRequest(
  {
    method: "POST",
    path: "/api/v1/events",
    body: {
      title: "Deployment Completed",
      text: "Application v2.1.0 deployed to production",
      alert_type: "info", // info, warning, error, success
      priority: "normal", // normal, low
      tags: ["env:production", "service:api", "version:2.1.0"],
      source_type_name: "deployment",
    },
  },
  { response: EventResponseSchema },
);

console.log(`Event created: ${result.event.url}`);
```

### Get Host Information

```typescript
const HostSchema = z.object({
  host_name: z.string(),
  id: z.number(),
  up: z.boolean(),
  is_muted: z.boolean(),
  apps: z.array(z.string()),
  tags_by_source: z.record(z.array(z.string())),
  last_reported_time: z.number(),
  meta: z
    .object({
      agent_version: z.string().optional(),
      platform: z.string().optional(),
    })
    .optional(),
});

const ListHostsResponseSchema = z.object({
  host_list: z.array(HostSchema),
  total_matching: z.number(),
  total_returned: z.number(),
});

const result = await ctx.integrations.datadog.apiRequest(
  {
    method: "GET",
    path: "/api/v1/hosts",
    params: {
      filter: "env:production",
      sort_field: "apps",
      sort_dir: "asc",
      count: 100,
    },
  },
  { response: ListHostsResponseSchema },
);

result.host_list.forEach((host) => {
  console.log(`${host.host_name}: ${host.up ? "UP" : "DOWN"}`);
});
```

### Submit Metrics

```typescript
const SubmitMetricsResponseSchema = z.object({
  status: z.string(),
});

const now = Math.floor(Date.now() / 1000);

const result = await ctx.integrations.datadog.apiRequest(
  {
    method: "POST",
    path: "/api/v2/series",
    body: {
      series: [
        {
          metric: "custom.orders.count",
          type: 1, // 1=count, 2=rate, 3=gauge
          points: [
            {
              timestamp: now,
              value: 42,
            },
          ],
          tags: ["env:production", "service:checkout"],
        },
      ],
    },
  },
  { response: SubmitMetricsResponseSchema },
);
```

### Search Logs

```typescript
const LogSchema = z.object({
  id: z.string(),
  attributes: z.object({
    timestamp: z.string(),
    status: z.string(),
    service: z.string().optional(),
    message: z.string().optional(),
  }),
  type: z.literal("log"),
});

const SearchLogsResponseSchema = z.object({
  data: z.array(LogSchema),
  meta: z
    .object({
      page: z
        .object({
          after: z.string().optional(),
        })
        .optional(),
    })
    .optional(),
});

const result = await ctx.integrations.datadog.apiRequest(
  {
    method: "POST",
    path: "/api/v2/logs/events/search",
    body: {
      filter: {
        query: "service:api status:error",
        from: "now-1h",
        to: "now",
      },
      sort: "-timestamp",
      page: {
        limit: 50,
      },
    },
  },
  { response: SearchLogsResponseSchema },
);

result.data.forEach((log) => {
  console.log(`[${log.attributes.status}] ${log.attributes.message}`);
});
```

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Common Pitfalls

### No Specialized Methods

```typescript
// WRONG - These methods do not exist
await datadog.queryMetrics({ ... });
await datadog.createMonitor({ ... });

// CORRECT - Use apiRequest
await ctx.integrations.datadog.apiRequest(
  { method: "GET", path: "/api/v1/query", params: { ... } },
  { response: QueryMetricsResponseSchema }
);
```

### API Version in Path

Datadog uses different API versions for different endpoints:

```typescript
// v1 endpoints
const path = "/api/v1/monitor";
const path = "/api/v1/query";
const path = "/api/v1/events";

// v2 endpoints (newer)
const path = "/api/v2/series";
const path = "/api/v2/logs/events/search";

// Check documentation for correct version
```

### Timestamps in Seconds

Datadog uses Unix timestamps in seconds, not milliseconds:

```typescript
// WRONG - Milliseconds
const from = Date.now();

// CORRECT - Seconds
const from = Math.floor(Date.now() / 1000);

// For queries
const params = {
  from: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
  to: Math.floor(Date.now() / 1000),
};
```

### Metric Query Syntax

Use Datadog's query language correctly:

```typescript
// Basic metric query
const query = "avg:system.cpu.user{*}";

// With tags/filters
const query = "avg:system.cpu.user{env:production,host:web-01}";

// Aggregation functions: avg, sum, min, max, count
const query = "sum:custom.orders.count{*}.as_count()";

// Time aggregation
const query = "avg(last_5m):avg:system.cpu.user{*}";

// Multiple metrics
const query = "avg:system.cpu.user{*}, avg:system.cpu.system{*}";
```

### Monitor Query Format

Monitor queries have specific format requirements:

```typescript
// Metric alert format
const query = "avg(last_5m):avg:system.cpu.user{env:production} > 90";

// Change alert
const query = "change(avg(last_5m),last_15m):avg:system.memory.used{*} > 100";

// Anomaly detection
const query = "avg(last_4h):anomalies(avg:system.cpu.user{*}, 'basic', 2) >= 1";
```

### Pointlist Format

Metric query results use tuples, not objects:

```typescript
// Datadog returns pointlist as array of [timestamp, value] tuples
const schema = z.object({
  pointlist: z.array(z.tuple([z.number(), z.number().nullable()])),
});

// Access values
result.pointlist.forEach(([timestamp, value]) => {
  console.log(`${new Date(timestamp * 1000)}: ${value}`);
});
```

### Null Values in Metrics

Metric values can be null for gaps in data:

```typescript
// WRONG - Assuming values exist
const value = point[1].toFixed(2); // Error if null

// CORRECT - Handle nulls
const value = point[1] !== null ? point[1].toFixed(2) : "N/A";
```

### Tags Format

Tags use key:value format:

```typescript
// CORRECT format
const tags = ["env:production", "service:api", "team:platform"];

// WRONG - Missing colon for key:value pairs
const tags = ["production", "api"]; // These are valid but less useful

// Filtering by tags
const params = {
  tags: "env:production,service:api", // Comma-separated
};
```

### Site-Specific Endpoints

Different Datadog sites have different base URLs:

```typescript
// US1 (default): api.datadoghq.com
// US3: api.us3.datadoghq.com
// US5: api.us5.datadoghq.com
// EU: api.datadoghq.eu
// AP1: api.ap1.datadoghq.com

// This is typically configured in the integration settings
```

## Error Handling

```typescript
import { RestApiValidationError } from "@superblocksteam/sdk-api";

try {
  const result = await ctx.integrations.datadog.apiRequest(
    { method: "GET", path: "/api/v1/query", params: { ... } },
    { response: QueryMetricsResponseSchema }
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Validation failed:", error.details.zodError);
  }
}
```

## API Reference

- [Datadog API Documentation](https://docs.datadoghq.com/api/)
- [Metrics](https://docs.datadoghq.com/api/latest/metrics/)
- [Monitors](https://docs.datadoghq.com/api/latest/monitors/)
- [Events](https://docs.datadoghq.com/api/latest/events/)
- [Logs](https://docs.datadoghq.com/api/latest/logs/)
