Claude-code-plugins-plus-skills hubspot-observability
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/hubspot-pack/skills/hubspot-observability" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-hubspot-observability && rm -rf "$T"
manifest:
plugins/saas-packs/hubspot-pack/skills/hubspot-observability/SKILL.mdsource content
HubSpot Observability
Overview
Instrument HubSpot API calls with Prometheus metrics, OpenTelemetry tracing, and structured logging to monitor CRM integration health.
Prerequisites
- Prometheus or compatible metrics backend
- OpenTelemetry SDK (optional, for tracing)
- Structured logging library (pino recommended)
Instructions
Step 1: Prometheus Metrics
import { Counter, Histogram, Gauge, Registry } from 'prom-client'; const registry = new Registry(); const hubspotRequests = new Counter({ name: 'hubspot_api_requests_total', help: 'Total HubSpot API requests', labelNames: ['method', 'object_type', 'status'], registers: [registry], }); const hubspotLatency = new Histogram({ name: 'hubspot_api_request_duration_seconds', help: 'HubSpot API request duration', labelNames: ['method', 'object_type'], buckets: [0.05, 0.1, 0.25, 0.5, 1, 2, 5], registers: [registry], }); const hubspotRateLimit = new Gauge({ name: 'hubspot_rate_limit_remaining', help: 'HubSpot daily rate limit remaining', labelNames: ['type'], registers: [registry], }); const hubspotErrors = new Counter({ name: 'hubspot_api_errors_total', help: 'HubSpot API errors by category', labelNames: ['status_code', 'category'], registers: [registry], });
Step 2: Instrumented Client Wrapper
import * as hubspot from '@hubspot/api-client'; class InstrumentedHubSpotClient { private client: hubspot.Client; constructor() { this.client = new hubspot.Client({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN!, numberOfApiCallRetries: 3, }); } async tracked<T>( method: string, objectType: string, operation: () => Promise<T> ): Promise<T> { const timer = hubspotLatency.startTimer({ method, object_type: objectType }); try { const result = await operation(); hubspotRequests.inc({ method, object_type: objectType, status: 'success' }); return result; } catch (error: any) { const statusCode = error?.code || error?.statusCode || 500; const category = error?.body?.category || 'UNKNOWN'; hubspotRequests.inc({ method, object_type: objectType, status: 'error' }); hubspotErrors.inc({ status_code: String(statusCode), category }); throw error; } finally { timer(); } } // Example: instrumented contact operations async getContact(id: string, properties: string[]) { return this.tracked('GET', 'contacts', () => this.client.crm.contacts.basicApi.getById(id, properties) ); } async createContact(properties: Record<string, string>) { return this.tracked('POST', 'contacts', () => this.client.crm.contacts.basicApi.create({ properties, associations: [] }) ); } async searchContacts(query: any) { return this.tracked('SEARCH', 'contacts', () => this.client.crm.contacts.searchApi.doSearch(query) ); } async batchReadContacts(ids: string[], properties: string[]) { return this.tracked('BATCH_READ', 'contacts', () => this.client.crm.contacts.batchApi.read({ inputs: ids.map(id => ({ id })), properties, propertiesWithHistory: [], }) ); } }
Step 3: Structured Logging
import pino from 'pino'; const logger = pino({ name: 'hubspot-integration', level: process.env.LOG_LEVEL || 'info', serializers: { // Redact access tokens from logs err: pino.stdSerializers.err, hubspot: (data: any) => ({ ...data, accessToken: undefined, }), }, }); // Log HubSpot operations with context function logHubSpotOp(operation: string, data: Record<string, any>, durationMs: number) { logger.info({ service: 'hubspot', operation, durationMs, ...data, }, `HubSpot ${operation} completed`); } // Log errors with correlation IDs function logHubSpotError(operation: string, error: any) { logger.error({ service: 'hubspot', operation, statusCode: error?.code || error?.statusCode, category: error?.body?.category, correlationId: error?.body?.correlationId, message: error?.body?.message || error.message, }, `HubSpot ${operation} failed`); }
Step 4: Alert Configuration
# hubspot_alerts.yaml (Prometheus AlertManager) groups: - name: hubspot_alerts rules: - alert: HubSpotHighErrorRate expr: | rate(hubspot_api_errors_total[5m]) / rate(hubspot_api_requests_total[5m]) > 0.05 for: 5m labels: severity: warning annotations: summary: "HubSpot API error rate > 5%" description: "{{ $value | humanizePercentage }} error rate" - alert: HubSpotHighLatency expr: | histogram_quantile(0.95, rate(hubspot_api_request_duration_seconds_bucket[5m]) ) > 3 for: 5m labels: severity: warning annotations: summary: "HubSpot API P95 latency > 3s" - alert: HubSpotRateLimitLow expr: hubspot_rate_limit_remaining{type="daily"} < 50000 for: 1m labels: severity: warning annotations: summary: "HubSpot daily rate limit below 10%" - alert: HubSpotAuthFailure expr: increase(hubspot_api_errors_total{status_code="401"}[5m]) > 0 for: 1m labels: severity: critical annotations: summary: "HubSpot authentication failure -- token may be revoked"
Step 5: Metrics Endpoint
import express from 'express'; const app = express(); app.get('/metrics', async (req, res) => { res.set('Content-Type', registry.contentType); res.send(await registry.metrics()); }); // Update rate limit gauge periodically setInterval(async () => { try { const response = await fetch( 'https://api.hubapi.com/crm/v3/objects/contacts?limit=1', { headers: { Authorization: `Bearer ${process.env.HUBSPOT_ACCESS_TOKEN}` } } ); const remaining = response.headers.get('x-hubspot-ratelimit-daily-remaining'); if (remaining) { hubspotRateLimit.set({ type: 'daily' }, parseInt(remaining)); } } catch { /* ignore monitoring errors */ } }, 60000);
Output
- Prometheus metrics: request count, latency histogram, error rate, rate limit gauge
- Instrumented client wrapper tracking all HubSpot operations
- Structured logging with correlation IDs and redacted secrets
- AlertManager rules for error rate, latency, rate limits, and auth failures
endpoint for Prometheus scraping/metrics
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Missing metrics | Operations not using instrumented client | Wrap all calls through |
| High cardinality | Too many label values | Limit labels to method + object_type |
| Alert storms | Thresholds too sensitive | Adjust duration and percentages |
| Logging PII | Contact data in logs | Use serializers to redact sensitive fields |
Resources
Next Steps
For incident response, see
hubspot-incident-runbook.