| name | linear-observability |
| description | Implement monitoring, logging, and alerting for Linear integrations.
Use when setting up metrics collection, creating dashboards,
or configuring alerts for Linear API usage.
Trigger with phrases like "linear monitoring", "linear observability",
"linear metrics", "linear logging", "monitor linear integration".
|
| allowed-tools | Read, Write, Edit, Grep |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Linear Observability
Overview
Comprehensive monitoring, logging, and alerting for Linear integrations.
Prerequisites
- Linear integration deployed
- Metrics infrastructure (Prometheus, Datadog, etc.)
- Logging infrastructure (ELK, CloudWatch, etc.)
- Alerting system configured
Instructions
Step 1: Metrics Collection
import { Counter, Histogram, Gauge, Registry } from "prom-client";
const registry = new Registry();
export const linearRequestsTotal = new Counter({
name: "linear_api_requests_total",
help: "Total Linear API requests",
labelNames: ["operation", "status"],
registers: [registry],
});
export const linearRequestDuration = new Histogram({
name: "linear_api_request_duration_seconds",
help: "Linear API request duration in seconds",
labelNames: ["operation"],
buckets: [0.1, 0.25, 0.5, 1, 2.5, 5, 10],
registers: [registry],
});
export const linearComplexityCost = new Histogram({
name: "linear_api_complexity_cost",
help: "Linear API query complexity cost",
labelNames: ["operation"],
buckets: [10, 50, 100, 250, 500, 1000, 2500],
registers: [registry],
});
export const linearRateLimitRemaining = new Gauge({
name: "linear_rate_limit_remaining",
help: "Remaining Linear API rate limit",
registers: [registry],
});
export const linearComplexityRemaining = new Gauge({
name: "linear_complexity_remaining",
help: "Remaining Linear complexity quota",
registers: [registry],
});
export const linearWebhooksReceived = new Counter({
name: "linear_webhooks_received_total",
help: "Total Linear webhooks received",
labelNames: ["type", "action"],
registers: [registry],
});
export const linearWebhookProcessingDuration = new Histogram({
name: "linear_webhook_processing_duration_seconds",
help: "Linear webhook processing duration",
labelNames: ["type"],
buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5],
registers: [registry],
});
export const linearCacheHits = new Counter({
name: "linear_cache_hits_total",
help: "Total Linear cache hits",
registers: [registry],
});
export const linearCacheMisses = new Counter({
name: "linear_cache_misses_total",
help: "Total Linear cache misses",
registers: [registry],
});
export { registry };
Step 2: Instrumented Client Wrapper
import { LinearClient } from "@linear/sdk";
import {
linearRequestsTotal,
linearRequestDuration,
linearRateLimitRemaining,
linearComplexityRemaining,
} from "./metrics";
export function createInstrumentedClient(apiKey: string): LinearClient {
const client = new LinearClient({
apiKey,
fetch: async (url, init) => {
const operation = extractOperationName(init?.body);
const timer = linearRequestDuration.startTimer({ operation });
try {
const response = await fetch(url, init);
const remaining = response.headers.get("x-ratelimit-remaining");
const complexity = response.headers.get("x-complexity-remaining");
if (remaining) linearRateLimitRemaining.set(parseInt(remaining));
if (complexity) linearComplexityRemaining.set(parseInt(complexity));
status = response. ? : ;
linearRequestsTotal.({ operation, status });
();
response;
} (error) {
linearRequestsTotal.({ operation, : });
();
error;
}
},
});
client;
}
(): {
(!body || body !== ) ;
{
parsed = .(body);
match = parsed.?.();
match?.[] || ;
} {
;
}
}
Step 3: Structured Logging
import pino from "pino";
export const logger = pino({
level: process.env.LOG_LEVEL || "info",
formatters: {
level: (label) => ({ level: label }),
},
base: {
service: "linear-integration",
environment: process.env.NODE_ENV,
},
});
export const linearLogger = logger.child({ component: "linear" });
export function logApiCall(operation: string, duration: number, success: boolean) {
linearLogger.info({
event: "api_call",
operation,
duration_ms: duration,
success,
});
}
export function logWebhook(type: string, action: string, id: ) {
linearLogger.({
: ,
: ,
: action,
: id,
});
}
() {
linearLogger.({
: ,
: error.,
: error.,
...context,
});
}
Step 4: Health Check Endpoint
import { LinearClient } from "@linear/sdk";
import { registry } from "../lib/metrics";
interface HealthStatus {
status: "healthy" | "degraded" | "unhealthy";
checks: {
linear_api: { status: string; latency_ms?: number; error?: string };
cache: { status: string; hit_rate?: number };
rate_limit: { status: string; remaining?: number; percentage?: number };
};
timestamp: string;
}
export async function healthCheck(client: LinearClient): Promise<HealthStatus> {
const checks: HealthStatus["checks"] = {
linear_api: { status: "unknown" },
cache: { status: },
: { : },
};
start = .();
{
client.;
checks. = {
: ,
: .() - start,
};
} (error) {
checks. = {
: ,
: error ? error. : ,
};
}
metrics = registry.();
rateLimitMetric = metrics.( m. === );
(rateLimitMetric) {
remaining = (rateLimitMetric ).?.[]?. || ;
percentage = (remaining / ) * ;
checks. = {
: percentage > ? : percentage > ? : ,
remaining,
: .(percentage),
};
}
statuses = .(checks).( c.);
: [] = ;
(statuses.()) status = ;
(statuses.()) status = ;
{
status,
checks,
: ().(),
};
}
Step 5: Alerting Rules
groups:
- name: linear-integration
rules:
- alert: LinearHighErrorRate
expr: |
sum(rate(linear_api_requests_total{status="error"}[5m]))
/ sum(rate(linear_api_requests_total[5m])) > 0.05
for: 5m
labels:
severity: warning
annotations:
summary: High Linear API error rate
description: "Linear API error rate is {{ $value | humanizePercentage }}"
- alert: LinearRateLimitLow
expr: linear_rate_limit_remaining < 100
for: 2m
labels:
severity: warning
annotations:
summary: Linear rate limit running low
description: "Only {{ $value }} requests remaining in rate limit window"
Step 6: Grafana Dashboard
{
"dashboard": {
"title": "Linear Integration",
"panels": [
{
"title": "API Request Rate",
"type": "graph",
"targets": [
{
"expr": "sum(rate(linear_api_requests_total[5m])) by (status)",
"legendFormat": "{{ status }}"
}
]
},
{
"title": "Request Latency (p95)",
"type": "gauge",
"targets": [
{
"expr": "histogram_quantile(0.95, rate(linear_api_request_duration_seconds_bucket[5m]))"
}
]
Error Handling
| Error | Cause | Solution |
|---|
Metrics not collecting | Missing instrumentation | Add metrics to client wrapper |
Alerts not firing | Wrong threshold | Adjust alert thresholds |
Missing labels | Logger misconfigured | Check logger configuration |
Resources
Next Steps
Create incident runbooks with linear-incident-runbook.