Skip to main content
miro-observability Set up observability for Miro REST API v2 integrations with Prometheus metrics,
OpenTelemetry traces, structured logging, and Grafana dashboards.
Trigger with phrases like "miro monitoring", "miro metrics",
"miro observability", "monitor miro", "miro alerts", "miro tracing".
Zur Installation springen Skills Marktplatz Entdecken und erkunden Sie KI-Skills, die von der Community erstellt wurden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Prompt kopierenPrompt-Details anzeigen Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
npx skills add https://github.com/jeremylongshore/claude-code-plugins-plus-skills --skill miro-observabilityDer Befehl bleibt in einer Zeile. Scrollen Sie horizontal, um ihn vor dem Kopieren vollständig zu prüfen.
Sie bevorzugen eine lokale Kopie? Laden Sie die Dateien herunter, die SkillsMP derzeit vorliegen.
ZIP herunterladen Herunterladen... Mehr aus diesem Repository Implement user sign-up and sign-in flows with Clerk.
Use when building authentication UI, customizing sign-in experience,
or implementing OAuth social login.
Trigger with phrases like "clerk sign-in", "clerk sign-up",
"clerk login flow", "clerk OAuth", "clerk social login".
Implement session management and middleware with Clerk.
Use when managing user sessions, configuring route protection,
or implementing token refresh and custom JWT templates.
Trigger with phrases like "clerk session", "clerk middleware",
"clerk route protection", "clerk token", "clerk JWT".
Configure enterprise SSO, role-based access control, and organization management.
Use when implementing SSO integration, configuring role-based permissions,
or setting up organization-level controls.
Trigger with phrases like "clerk SSO", "clerk RBAC",
"clerk enterprise", "clerk roles", "clerk permissions", "clerk organizations".
Verwandte Berufe SOC
Basierend auf der SOC-Berufsklassifikation
name miro-observability description Set up observability for Miro REST API v2 integrations with Prometheus metrics,
OpenTelemetry traces, structured logging, and Grafana dashboards.
Trigger with phrases like "miro monitoring", "miro metrics",
"miro observability", "monitor miro", "miro alerts", "miro tracing".
allowed-tools Read, Write, Edit version 1.6.0 license MIT author Jeremy Longshore <jeremy@intentsolutions.io> tags ["saas","miro","observability","monitoring"] compatibility Designed for Claude Code
Miro Observability
Overview
Comprehensive monitoring for Miro REST API v2 integrations: Prometheus metrics for request rates and latency, OpenTelemetry traces for request flow, structured logging, and alerting for rate limit and error conditions.
Key Metrics
Metric Type Labels Purpose miro_requests_totalCounter method, endpoint, status Request volume miro_request_duration_secondsHistogram method, endpoint Latency distribution miro_errors_totalCounter error_type, endpoint Error tracking miro_rate_limit_remainingGauge — Credit headroom miro_rate_limit_credits_usedGauge — Credit consumption miro_webhook_events_totalCounter event_type, item_type Webhook volume miro_token_refresh_totalCounter status OAuth health
Prometheus Metrics
import { Registry , Counter , Histogram , Gauge } from 'prom-client' ;
const registry = new Registry ();
registry.setDefaultLabels ({ app : 'miro-integration' });
const requestCounter = new Counter ({
name : 'miro_requests_total' ,
help : 'Total Miro REST API v2 requests' ,
: [ , , ] ,
: [registry],
});
requestDuration = ({
: ,
: ,
: [ , ] ,
: [ , , , , , , , ],
: [registry],
});
errorCounter = ({
: ,
: ,
: [ , ] ,
: [registry],
});
rateLimitRemaining = ({
: ,
: ,
: [registry],
});
rateLimitUsed = ({
: ,
: ,
: [registry],
});
webhookCounter = ({
: ,
: ,
: [ , ] ,
: [registry],
});
labelNames
'method'
'endpoint'
'status'
as
const
registers
const
new
Histogram
name
'miro_request_duration_seconds'
help
'Miro API request latency'
labelNames
'method'
'endpoint'
as
const
buckets
0.05
0.1
0.25
0.5
1
2.5
5
10
registers
const
new
Counter
name
'miro_errors_total'
help
'Miro API errors by type'
labelNames
'error_type'
'endpoint'
as
const
registers
const
new
Gauge
name
'miro_rate_limit_remaining'
help
'Miro rate limit credits remaining'
registers
const
new
Gauge
name
'miro_rate_limit_credits_used'
help
'Miro rate limit credits used in current window'
registers
const
new
Counter
name
'miro_webhook_events_total'
help
'Miro webhook events received'
labelNames
'event_type'
'item_type'
as
const
registers
Instrumented API Client class InstrumentedMiroClient {
async fetch<T>(path : string , method = 'GET' , body ?: unknown ): Promise <T> {
const endpoint = this .normalizeEndpoint (path);
const timer = requestDuration.startTimer ({ method, endpoint });
try {
const response = await fetch (`https://api.miro.com${path} ` , {
method,
headers : {
'Authorization' : `Bearer ${this .token} ` ,
'Content-Type' : 'application/json' ,
},
...(body ? { body : JSON .stringify (body) } : {}),
});
const remaining = response.headers .get ('X-RateLimit-Remaining' );
const limit = response.headers .get ('X-RateLimit-Limit' );
if (remaining) rateLimitRemaining.set (parseInt (remaining));
if (remaining && limit) {
rateLimitUsed.set (parseInt (limit) - parseInt (remaining));
}
requestCounter.inc ({ method, endpoint, status : String (response.status ) });
if (!response.ok ) {
const errorType = response.status === 429 ? 'rate_limit'
: response.status === 401 ? 'auth'
: response.status >= 500 ? 'server'
: 'client' ;
errorCounter.inc ({ error_type : errorType, endpoint });
throw new MiroApiError (response.status , await response.text ());
}
return response.status === 204 ? null as T : await response.json ();
} catch (error) {
if (!(error instanceof MiroApiError )) {
errorCounter.inc ({ error_type : 'network' , endpoint });
}
throw error;
} finally {
timer ();
}
}
private normalizeEndpoint (path : string ): string {
return path
.replace (/\/boards\/[^/]+/ , '/boards/{id}' )
.replace (/\/items\/[^/]+/ , '/items/{id}' )
.replace (/\/sticky_notes\/[^/]+/ , '/sticky_notes/{id}' )
.replace (/\/shapes\/[^/]+/ , '/shapes/{id}' )
.replace (/\/connectors\/[^/]+/ , '/connectors/{id}' )
.replace (/\?.*$/ , '' );
}
}
OpenTelemetry Tracing import { trace, SpanStatusCode , context } from '@opentelemetry/api' ;
const tracer = trace.getTracer ('miro-client' , '1.0.0' );
async function tracedMiroFetch<T>(
path : string ,
method : string ,
body ?: unknown ,
): Promise <T> {
const endpoint = normalizeEndpoint (path);
return tracer.startActiveSpan (`miro.${method} ${endpoint} ` , async (span) => {
span.setAttribute ('miro.method' , method);
span.setAttribute ('miro.endpoint' , endpoint);
span.setAttribute ('miro.api_version' , 'v2' );
try {
const result = await instrumentedClient.fetch <T>(path, method, body);
span.setStatus ({ code : SpanStatusCode .OK });
return result;
} catch (error : any ) {
span.setStatus ({ code : SpanStatusCode .ERROR , message : error.message });
span.setAttribute ('miro.error_status' , error.status ?? 0 );
span.recordException (error);
throw error;
} finally {
span.end ();
}
});
}
Structured Logging import pino from 'pino' ;
const logger = pino ({
name : 'miro-integration' ,
level : process.env .LOG_LEVEL ?? 'info' ,
redact : ['token' , 'accessToken' , 'refreshToken' , 'Authorization' ],
});
function logMiroRequest (method : string , path : string , status : number , durationMs : number ) {
logger.info ({
service : 'miro' ,
event : 'api_request' ,
method,
path : normalizeEndpoint (path),
status,
durationMs : Math .round (durationMs),
rateLimitRemaining : currentRateLimitRemaining,
});
}
function logWebhookEvent (event : MiroBoardEvent ) {
logger.info ({
service : 'miro' ,
event : 'webhook_received' ,
eventType : event.type ,
itemType : event.item .type ,
boardId : event.boardId ,
itemId : event.item .id ,
});
}
Alert Rules (Prometheus AlertManager)
groups:
- name: miro_alerts
rules:
- alert: MiroHighErrorRate
expr: |
rate(miro_errors_total[5m]) /
rate(miro_requests_total[5m]) > 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "Miro API error rate > 5%"
dashboard: "https://grafana.myapp.com/d/miro"
- alert: MiroHighLatency
expr: |
histogram_quantile(0.95,
rate(miro_request_duration_seconds_bucket[5m])
) > 3
for: 5m
labels:
severity: warning
annotations:
summary: "Miro API P95 latency > 3 seconds"
- alert: MiroRateLimitLow
expr: miro_rate_limit_remaining < 5000
for: 1m
labels:
severity: critical
annotations:
summary: "Miro rate limit credits < 5000 remaining"
runbook: "Reduce request rate immediately. See miro-rate-limits skill."
- alert: MiroAuthFailures
expr: rate(miro_errors_total{error_type="auth"}[5m]) > 0
for: 2m
labels:
severity: critical
annotations:
summary: "Miro authentication failures detected"
runbook: "Check token expiry. Verify OAuth scopes."
- alert: MiroDown
expr: |
sum(rate(miro_requests_total{status=~"5.."}[5m])) /
sum(rate(miro_requests_total[5m])) > 0.5
for: 3m
labels:
severity: critical
annotations:
summary: "Miro API >50% server errors — check status.miro.com"
Grafana Dashboard Panels {
"panels" : [
{
"title" : "Miro Request Rate (req/s)" ,
"targets" : [ { "expr" : "sum(rate(miro_requests_total[1m]))" } ]
} ,
{
"title" : "Miro Latency P50/P95/P99" ,
"targets" : [
{ "expr" : "histogram_quantile(0.50, rate(miro_request_duration_seconds_bucket[5m]))" , "legendFormat" : "P50" } ,
{ "expr" : "histogram_quantile(0.95, rate(miro_request_duration_seconds_bucket[5m]))" , "legendFormat" : "P95" } ,
{ "expr" : "histogram_quantile(0.99, rate(miro_request_duration_seconds_bucket[5m]))" , "legendFormat" : "P99" }
]
} ,
{
"title" : "Rate Limit Credits Remaining" ,
"targets" : [ { "expr" : "miro_rate_limit_remaining" } ]
} ,
{
"title" : "Error Rate by Type" ,
"targets" : [ { "expr" : "sum by(error_type) (rate(miro_errors_total[5m]))" } ]
} ,
{
"title" : "Webhook Events by Type" ,
"targets" : [ { "expr" : "sum by(event_type, item_type) (rate(miro_webhook_events_total[5m]))" } ]
}
]
}
Metrics Endpoint app.get ('/metrics' , async (req, res) => {
res.set ('Content-Type' , registry.contentType );
res.send (await registry.metrics ());
});
Error Handling Issue Cause Solution High cardinality metrics Board/item IDs in labels Normalize endpoint paths Missing traces No context propagation Check OpenTelemetry SDK init Token in logs Inadequate redaction Use pino redact option Alert storms Thresholds too sensitive Increase for duration
Resources
Next Steps For incident response, see miro-incident-runbook.