Skip to main content Inicio Creadores dicklesworthstone pi_agent_rust evernote-observability
evernote-observability Implement observability for Evernote integrations.
Use when setting up monitoring, logging, tracing,
or alerting for Evernote applications.
Trigger with phrases like "evernote monitoring", "evernote logging",
"evernote metrics", "evernote observability".
Ir a la instalación Skills Marketplace Descubre y explora habilidades de IA creadas por la comunidad.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Copiar promptMostrar detalles del prompt Un comando directo omite el prompt de revisión. Revisa el origen antes de ejecutarlo.
npx skills add https://github.com/Dicklesworthstone/pi_agent_rust --skill evernote-observabilityEl comando permanece en una sola línea. Desplázate horizontalmente para revisarlo antes de copiarlo.
¿Prefieres una copia local? Descarga los archivos que SkillsMP tiene disponibles ahora.
Descargar Zip Descargando... Ocupaciones relacionadas SOC
Basado en la clasificación ocupacional SOC
name evernote-observability description Implement observability for Evernote integrations.
Use when setting up monitoring, logging, tracing,
or alerting for Evernote applications.
Trigger with phrases like "evernote monitoring", "evernote logging",
"evernote metrics", "evernote observability".
allowed-tools Read, Write, Edit, Bash(npm:*), Grep version 1.0.0 license MIT author Jeremy Longshore <jeremy@intentsolutions.io>
Evernote Observability
Overview
Comprehensive observability setup for Evernote integrations including metrics, logging, tracing, and alerting.
Prerequisites
Monitoring infrastructure (Prometheus, Datadog, etc.)
Log aggregation (ELK, CloudWatch, etc.)
Alerting system
Instructions
Step 1: Metrics Collection
const prometheus = ( );
prometheus. ({ : });
apiCallCounter = prometheus. ({
: ,
: ,
: [ , , ]
});
apiCallDuration = prometheus. ({
: ,
: ,
: [ ],
: [ , , , , , , ]
});
rateLimitCounter = prometheus. ({
: ,
:
});
rateLimitWaitGauge = prometheus. ({
: ,
:
});
cacheHitCounter = prometheus. ({
: ,
: ,
: [ ]
});
cacheMissCounter = prometheus. ({
: ,
: ,
: [ ]
});
authCounter = prometheus. ({
: ,
: ,
: [ , ]
});
activeTokensGauge = prometheus. ({
: ,
:
});
quotaUsageGauge = prometheus. ({
: ,
: ,
: [ ]
});
. = {
apiCallCounter,
apiCallDuration,
rateLimitCounter,
rateLimitWaitGauge,
cacheHitCounter,
cacheMissCounter,
authCounter,
activeTokensGauge,
quotaUsageGauge,
: prometheus.
};
require
'prom-client'
collectDefaultMetrics
prefix
'evernote_'
const
new
Counter
name
'evernote_api_calls_total'
help
'Total number of Evernote API calls'
labelNames
'operation'
'status'
'sandbox'
const
new
Histogram
name
'evernote_api_call_duration_seconds'
help
'Duration of Evernote API calls'
labelNames
'operation'
buckets
0.1
0.25
0.5
1
2
5
10
const
new
Counter
name
'evernote_rate_limits_total'
help
'Total number of rate limit hits'
const
new
Gauge
name
'evernote_rate_limit_wait_seconds'
help
'Current rate limit wait time'
const
new
Counter
name
'evernote_cache_hits_total'
help
'Total cache hits'
labelNames
'operation'
const
new
Counter
name
'evernote_cache_misses_total'
help
'Total cache misses'
labelNames
'operation'
const
new
Counter
name
'evernote_auth_total'
help
'Total authentication attempts'
labelNames
'status'
'type'
const
new
Gauge
name
'evernote_active_tokens'
help
'Number of active user tokens'
const
new
Gauge
name
'evernote_quota_usage_bytes'
help
'Current quota usage in bytes'
labelNames
'user_id'
module
exports
register
register
Step 2: Instrumented Client
const Evernote = require ('evernote' );
const metrics = require ('../monitoring/metrics' );
const logger = require ('../logging/logger' );
class InstrumentedEvernoteClient {
constructor (accessToken, options = {} ) {
this .client = new Evernote .Client ({
token : accessToken,
sandbox : options.sandbox || false
});
this .userId = options.userId ;
this .sandbox = options.sandbox ;
this ._noteStore = null ;
}
get noteStore () {
if (!this ._noteStore ) {
this ._noteStore = this .wrapStore (
this .client .getNoteStore (),
'NoteStore'
);
}
return this ._noteStore ;
}
wrapStore (store, storeName ) {
const self = this ;
return new Proxy (store, {
get (target, prop ) {
const original = target[prop];
if (typeof original !== 'function' ) {
return original;
}
return async (...args) => {
const operation = `${storeName} .${prop} ` ;
const startTime = Date .now ();
const endTimer = metrics.apiCallDuration .startTimer ({ operation });
try {
const result = await original.apply (target, args);
const duration = (Date .now () - startTime) / 1000 ;
metrics.apiCallCounter .inc ({
operation,
status : 'success' ,
sandbox : String (self.sandbox )
});
logger.debug ('Evernote API call' , {
operation,
duration,
userId : self.userId
});
return result;
} catch (error) {
metrics.apiCallCounter .inc ({
operation,
status : error.errorCode ? `error_${error.errorCode} ` : 'error' ,
sandbox : String (self.sandbox )
});
if (error.errorCode === 19 ) {
metrics.rateLimitCounter .inc ();
metrics.rateLimitWaitGauge .set (error.rateLimitDuration || 0 );
logger.warn ('Rate limit hit' , {
operation,
userId : self.userId ,
waitTime : error.rateLimitDuration
});
} else {
logger.error ('Evernote API error' , {
operation,
errorCode : error.errorCode ,
parameter : error.parameter ,
userId : self.userId
});
}
throw error;
} finally {
endTimer ();
}
};
}
});
}
}
module .exports = InstrumentedEvernoteClient ;
Step 3: Structured Logging
const winston = require ('winston' );
const logger = winston.createLogger ({
level : process.env .LOG_LEVEL || 'info' ,
format : winston.format .combine (
winston.format .timestamp (),
winston.format .errors ({ stack : true }),
winston.format .json ()
),
defaultMeta : {
service : 'evernote-integration' ,
environment : process.env .NODE_ENV
},
transports : [
new winston.transports .Console ({
format : process.env .NODE_ENV === 'development'
? winston.format .combine (
winston.format .colorize (),
winston.format .simple ()
)
: winston.format .json ()
})
]
});
if (process.env .NODE_ENV === 'production' ) {
logger.add (new winston.transports .File ({
filename : 'logs/error.log' ,
level : 'error' ,
maxsize : 10 * 1024 * 1024 ,
maxFiles : 5
}));
logger.add (new winston.transports .File ({
filename : 'logs/combined.log' ,
maxsize : 10 * 1024 * 1024 ,
maxFiles : 5
}));
}
const redactPatterns = [
/S=s\d+:U=[^:]+:[^:]+:[a-f0-9]+/gi ,
/bearer\s+[^\s]+/gi ,
/api[_-]?key[=:]\s*[^\s,}]+/gi
];
function redact (message ) {
if (typeof message !== 'string' ) return message;
let redacted = message;
for (const pattern of redactPatterns) {
redacted = redacted.replace (pattern, '[REDACTED]' );
}
return redacted;
}
const originalLog = logger.log .bind (logger);
logger.log = function (level, message, meta = {} ) {
if (typeof message === 'string' ) {
message = redact (message);
}
if (meta && typeof meta === 'object' ) {
meta = JSON .parse (redact (JSON .stringify (meta)));
}
return originalLog (level, message, meta);
};
module .exports = logger;
Step 4: Distributed Tracing
const { NodeTracerProvider } = require ('@opentelemetry/sdk-trace-node' );
const { SimpleSpanProcessor } = require ('@opentelemetry/sdk-trace-base' );
const { JaegerExporter } = require ('@opentelemetry/exporter-jaeger' );
const { Resource } = require ('@opentelemetry/resources' );
const { SemanticResourceAttributes } = require ('@opentelemetry/semantic-conventions' );
const { trace, context, SpanKind } = require ('@opentelemetry/api' );
const provider = new NodeTracerProvider ({
resource : new Resource ({
[SemanticResourceAttributes .SERVICE_NAME ]: 'evernote-integration'
})
});
if (process.env .JAEGER_ENDPOINT ) {
const exporter = new JaegerExporter ({
endpoint : process.env .JAEGER_ENDPOINT
});
provider.addSpanProcessor (new SimpleSpanProcessor (exporter));
}
provider.register ();
const tracer = trace.getTracer ('evernote-integration' );
function traceOperation (operation, fn ) {
return async (...args) => {
const span = tracer.startSpan (`evernote.${operation} ` , {
kind : SpanKind .CLIENT ,
attributes : {
'evernote.operation' : operation
}
});
try {
const result = await context.with (
trace.setSpan (context.active (), span),
() => fn (...args)
);
span.setStatus ({ code : 0 });
return result;
} catch (error) {
span.setStatus ({
code : 2 ,
message : error.message
});
span.recordException (error);
span.setAttribute ('evernote.error_code' , error.errorCode );
throw error;
} finally {
span.end ();
}
};
}
module .exports = { tracer, traceOperation };
Step 5: Health and Readiness Endpoints
const express = require ('express' );
const metrics = require ('../monitoring/metrics' );
const router = express.Router ();
router.get ('/health/live' , (req, res ) => {
res.status (200 ).json ({ status : 'alive' });
});
router.get ('/health/ready' , async (req, res) => {
const checks = await runHealthChecks ();
const allHealthy = checks.every (c => c.status === 'healthy' );
res.status (allHealthy ? 200 : 503 ).json ({
status : allHealthy ? 'ready' : 'not_ready' ,
checks
});
});
router.get ('/health/detailed' , async (req, res) => {
const checks = await runHealthChecks ();
res.json ({
status : checks.every (c => c.status === 'healthy' ) ? 'healthy' : 'degraded' ,
timestamp : new Date ().toISOString (),
uptime : process.uptime (),
checks
});
});
router.get ('/metrics' , async (req, res) => {
res.set ('Content-Type' , metrics.register .contentType );
res.end (await metrics.register .metrics ());
});
async function runHealthChecks ( ) {
const checks = [];
try {
await db.query ('SELECT 1' );
checks.push ({ name : 'database' , status : 'healthy' });
} catch (error) {
checks.push ({ name : 'database' , status : 'unhealthy' , error : error.message });
}
try {
await redis.ping ();
checks.push ({ name : 'redis' , status : 'healthy' });
} catch (error) {
checks.push ({ name : 'redis' , status : 'unhealthy' , error : error.message });
}
const memUsage = process.memoryUsage ();
const heapPercent = (memUsage.heapUsed / memUsage.heapTotal ) * 100 ;
checks.push ({
name : 'memory' ,
status : heapPercent < 90 ? 'healthy' : 'warning' ,
heapUsedPercent : heapPercent.toFixed (1 )
});
return checks;
}
module .exports = router;
Step 6: Alert Rules
groups:
- name: evernote-alerts
rules:
- alert: EvernoteHighErrorRate
expr: |
sum(rate(evernote_api_calls_total{status=~"error.*"}[5m])) /
sum(rate(evernote_api_calls_total[5m])) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: High Evernote API error rate
description: "Error rate is {{ $value | humanizePercentage }} "
- alert: EvernoteRateLimited
expr: rate(evernote_rate_limits_total[5m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: Evernote rate limit detected
description: "Rate limits are being hit"
- alert: EvernoteHighLatency
expr: |
histogram_quantile(0.95, rate(evernote_api_call_duration_seconds_bucket[5m])) > 5
for: 5m
labels:
severity: warning
annotations:
summary: High Evernote API latency
description: "P95 latency is {{ $value }} s"
- alert: EvernoteAuthFailures
expr: rate(evernote_auth_total{status="failure"}[5m]) > 0.1
for: 5m
labels:
severity: critical
annotations:
summary: High authentication failure rate
description: "Auth failures: {{ $value }} per second"
- alert: EvernoteLowCacheHitRate
expr: |
sum(rate(evernote_cache_hits_total[5m])) /
(sum(rate(evernote_cache_hits_total[5m])) +
sum(rate(evernote_cache_misses_total[5m]))) < 0.5
for: 15m
labels:
severity: info
annotations:
summary: Low cache hit rate
description: "Cache hit rate is {{ $value | humanizePercentage }} "
Step 7: Grafana Dashboard {
"dashboard" : {
"title" : "Evernote Integration" ,
"panels" : [
{
"title" : "API Calls Rate" ,
"type" : "graph" ,
"targets" : [
{
"expr" : "sum(rate(evernote_api_calls_total[5m])) by (operation)" ,
"legendFormat" : "{{operation}}"
}
]
} ,
{
"title" : "Error Rate" ,
"type" : "graph" ,
"targets" : [
{
"expr" : "sum(rate(evernote_api_calls_total{status=~\"error.*\"}[5m])) / sum(rate(evernote_api_calls_total[5m])) * 100" ,
"legendFormat" : "Error %"
}
]
} ,
{
"title" : "API Latency (P50/P95/P99)" ,
"type" : "graph" ,
"targets" : [
{
"expr" : "histogram_quantile(0.5, rate(evernote_api_call_duration_seconds_bucket[5m]))" ,
"legendFormat" : "P50"
} ,
{
"expr" : "histogram_quantile(0.95, rate(evernote_api_call_duration_seconds_bucket[5m]))" ,
"legendFormat" : "P95"
} ,
{
"expr" : "histogram_quantile(0.99, rate(evernote_api_call_duration_seconds_bucket[5m]))" ,
"legendFormat" : "P99"
}
]
} ,
{
"title" : "Rate Limits" ,
"type" : "stat" ,
"targets" : [
{
"expr" : "sum(increase(evernote_rate_limits_total[1h]))" ,
"legendFormat" : "Rate Limits (1h)"
}
]
} ,
{
"title" : "Cache Hit Rate" ,
"type" : "gauge" ,
"targets" : [
{
"expr" : "sum(rate(evernote_cache_hits_total[5m])) / (sum(rate(evernote_cache_hits_total[5m])) + sum(rate(evernote_cache_misses_total[5m]))) * 100"
}
]
}
]
}
}
Output
Prometheus metrics collection
Instrumented Evernote client
Structured JSON logging
Distributed tracing with OpenTelemetry
Health check endpoints
Prometheus alert rules
Grafana dashboard configuration
Key Metrics Metric Type Purpose api_calls_total Counter Track API usage api_call_duration_seconds Histogram Latency monitoring rate_limits_total Counter Rate limit tracking cache_hits_total Counter Cache effectiveness auth_total Counter Auth success/failure
Resources
Next Steps For incident handling, see evernote-incident-runbook.