Claude-skill-registry lokalise-observability
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/lokalise-observability" ~/.claude/skills/majiayu000-claude-skill-registry-lokalise-observability && rm -rf "$T"
manifest:
skills/data/lokalise-observability/SKILL.mdsource content
Lokalise Observability
Overview
Set up comprehensive observability for Lokalise integrations with metrics, logging, and alerting.
Prerequisites
- Prometheus or compatible metrics backend
- Logging infrastructure (ELK, Datadog, etc.)
- Grafana or similar dashboarding tool
- AlertManager configured
Key Metrics
| Metric | Type | Description |
|---|---|---|
| Counter | Total API requests |
| Histogram | Request latency |
| Counter | Error count by type |
| Gauge | Rate limit headroom |
| Gauge | Total translation keys |
| Gauge | Translation progress |
| Counter | Webhook events received |
Instructions
Step 1: Prometheus Metrics Setup
import { Registry, Counter, Histogram, Gauge } from "prom-client"; const registry = new Registry(); // Request metrics const requestCounter = new Counter({ name: "lokalise_requests_total", help: "Total Lokalise API requests", labelNames: ["method", "endpoint", "status"], registers: [registry], }); const requestDuration = new Histogram({ name: "lokalise_request_duration_seconds", help: "Lokalise request duration", labelNames: ["method", "endpoint"], buckets: [0.1, 0.25, 0.5, 1, 2.5, 5, 10], registers: [registry], }); const errorCounter = new Counter({ name: "lokalise_errors_total", help: "Lokalise errors by type", labelNames: ["error_type", "endpoint"], registers: [registry], }); // Rate limit tracking const rateLimitRemaining = new Gauge({ name: "lokalise_rate_limit_remaining", help: "Remaining rate limit quota", registers: [registry], }); // Translation metrics const translationCoverage = new Gauge({ name: "lokalise_translation_coverage", help: "Translation coverage percentage", labelNames: ["project", "language"], registers: [registry], });
Step 2: Instrumented Client Wrapper
import { LokaliseApi } from "@lokalise/node-api"; export async function instrumentedRequest<T>( method: string, endpoint: string, operation: () => Promise<T> ): Promise<T> { const timer = requestDuration.startTimer({ method, endpoint }); try { const result = await operation(); requestCounter.inc({ method, endpoint, status: "success" }); return result; } catch (error: any) { requestCounter.inc({ method, endpoint, status: "error" }); errorCounter.inc({ error_type: error.code?.toString() || "unknown", endpoint, }); throw error; } finally { timer(); } } // Usage export class InstrumentedLokaliseClient { private client: LokaliseApi; constructor(apiKey: string) { this.client = new LokaliseApi({ apiKey }); } async listProjects() { return instrumentedRequest("GET", "/projects", () => this.client.projects().list() ); } async listKeys(projectId: string) { return instrumentedRequest("GET", "/keys", () => this.client.keys().list({ project_id: projectId }) ); } async downloadFiles(projectId: string, options: any) { return instrumentedRequest("POST", "/files/download", () => this.client.files().download(projectId, options) ); } }
Step 3: Structured Logging
import pino from "pino"; const logger = pino({ name: "lokalise", level: process.env.LOG_LEVEL || "info", formatters: { level: (label) => ({ level: label }), }, }); interface LokaliseLogContext { operation: string; projectId?: string; keyId?: number; locale?: string; duration?: number; error?: string; } export function logLokaliseOperation( level: "info" | "warn" | "error", message: string, context: LokaliseLogContext ) { logger[level]({ service: "lokalise", ...context, timestamp: new Date().toISOString(), }, message); } // Usage logLokaliseOperation("info", "Translation sync completed", { operation: "sync", projectId: "123456.abc", duration: 1500, }); logLokaliseOperation("error", "API request failed", { operation: "listKeys", projectId: "123456.abc", error: "Rate limit exceeded", });
Step 4: Health Check Endpoint
interface LokaliseHealth { status: "healthy" | "degraded" | "unhealthy"; latencyMs: number; rateLimitRemaining?: number; lastSync?: string; error?: string; } export async function checkLokaliseHealth(): Promise<LokaliseHealth> { const start = Date.now(); try { const client = new LokaliseApi({ apiKey: process.env.LOKALISE_API_TOKEN!, }); // Quick connectivity test await client.projects().list({ limit: 1 }); const latencyMs = Date.now() - start; return { status: latencyMs < 2000 ? "healthy" : "degraded", latencyMs, lastSync: process.env.LAST_TRANSLATION_SYNC, }; } catch (error: any) { return { status: "unhealthy", latencyMs: Date.now() - start, error: error.message, }; } } // Express endpoint app.get("/health/lokalise", async (req, res) => { const health = await checkLokaliseHealth(); const statusCode = health.status === "healthy" ? 200 : 503; res.status(statusCode).json(health); });
Step 5: Alert Rules
# prometheus/lokalise_alerts.yml groups: - name: lokalise_alerts rules: - alert: LokaliseHighErrorRate expr: | rate(lokalise_errors_total[5m]) / rate(lokalise_requests_total[5m]) > 0.05 for: 5m labels: severity: warning annotations: summary: "Lokalise error rate > 5%" description: "Error rate is {{ $value | printf \"%.2f\" }}%" - alert: LokaliseHighLatency expr: | histogram_quantile(0.95, rate(lokalise_request_duration_seconds_bucket[5m]) ) > 3 for: 5m labels: severity: warning annotations: summary: "Lokalise P95 latency > 3s" - alert: LokaliseRateLimitLow expr: lokalise_rate_limit_remaining < 2 for: 1m labels: severity: warning annotations: summary: "Lokalise rate limit nearly exhausted" - alert: LokaliseDown expr: up{job="lokalise"} == 0 for: 1m labels: severity: critical annotations: summary: "Lokalise integration is down"
Output
- Metrics collection enabled
- Structured logging implemented
- Health check endpoint
- Alert rules deployed
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Missing metrics | No instrumentation | Wrap client calls |
| Log flooding | Verbose level | Adjust log level |
| Alert storms | Wrong thresholds | Tune alert rules |
| Health false positive | Timeout too short | Increase timeout |
Examples
Grafana Dashboard Queries
{ "panels": [ { "title": "Lokalise Request Rate", "targets": [{ "expr": "rate(lokalise_requests_total[5m])" }] }, { "title": "Lokalise Latency P50/P95/P99", "targets": [ { "expr": "histogram_quantile(0.50, rate(lokalise_request_duration_seconds_bucket[5m]))" }, { "expr": "histogram_quantile(0.95, rate(lokalise_request_duration_seconds_bucket[5m]))" }, { "expr": "histogram_quantile(0.99, rate(lokalise_request_duration_seconds_bucket[5m]))" } ] }, { "title": "Translation Coverage by Language", "targets": [{ "expr": "lokalise_translation_coverage" }] } ] }
Collect Translation Coverage
async function updateTranslationCoverageMetrics(projectId: string) { const client = new LokaliseApi({ apiKey: process.env.LOKALISE_API_TOKEN!, }); const languages = await client.languages().list({ project_id: projectId }); for (const lang of languages.items) { translationCoverage.set( { project: projectId, language: lang.lang_iso }, lang.statistics?.progress ?? 0 ); } } // Run periodically setInterval(() => { updateTranslationCoverageMetrics(process.env.LOKALISE_PROJECT_ID!); }, 300000); // Every 5 minutes
Metrics Endpoint
app.get("/metrics", async (req, res) => { res.set("Content-Type", registry.contentType); res.send(await registry.metrics()); });
Resources
Next Steps
For incident response, see
lokalise-incident-runbook.