| 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 = require('prom-client');
prometheus.collectDefaultMetrics({ prefix: 'evernote_' });
const apiCallCounter = new prometheus.Counter({
name: 'evernote_api_calls_total',
help: 'Total number of Evernote API calls',
labelNames: ['operation', 'status', 'sandbox']
});
const apiCallDuration = new prometheus.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 rateLimitCounter = new prometheus.Counter({
name: 'evernote_rate_limits_total',
help: 'Total number of rate limit hits'
});
const rateLimitWaitGauge = new prometheus.Gauge({
name: 'evernote_rate_limit_wait_seconds',
help: 'Current rate limit wait time'
});
const cacheHitCounter = new prometheus.Counter({
name: 'evernote_cache_hits_total',
help: 'Total cache hits',
labelNames: ['operation']
});
const cacheMissCounter = new prometheus.Counter({
name: 'evernote_cache_misses_total',
help: 'Total cache misses',
labelNames: ['operation']
});
const authCounter = new prometheus.Counter({
name: 'evernote_auth_total',
help: 'Total authentication attempts',
labelNames: ['status', 'type']
});
const activeTokensGauge = new prometheus.Gauge({
name: 'evernote_active_tokens',
help: 'Number of active user tokens'
});
const quotaUsageGauge = new prometheus.Gauge({
name: 'evernote_quota_usage_bytes',
help: 'Current quota usage in bytes',
labelNames: ['user_id']
});
module.exports = {
apiCallCounter,
apiCallDuration,
rateLimitCounter,
rateLimitWaitGauge,
cacheHitCounter,
cacheMissCounter,
authCounter,
activeTokensGauge,
quotaUsageGauge,
register: prometheus.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;
}
() {
self = ;
(store, {
() {
original = target[prop];
( original !== ) {
original;
}
(...args) => {
operation = ;
startTime = .();
endTimer = metrics..({ operation });
{
result = original.(target, args);
duration = (.() - startTime) / ;
metrics..({
operation,
: ,
: (self.)
});
logger.(, {
operation,
duration,
: self.
});
result;
} (error) {
metrics..({
operation,
: error. ? : ,
: (self.)
});
(error. === ) {
metrics..();
metrics..(error. || );
logger.(, {
operation,
: self.,
: error.
});
} {
logger.(, {
operation,
: error.,
: error.,
: self.
});
}
error;
} {
();
}
};
}
});
}
}
. = ;
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( winston..({
: ,
: ,
: * * ,
:
}));
logger.( winston..({
: ,
: * * ,
:
}));
}
redactPatterns = [
,
,
];
() {
( message !== ) message;
redacted = message;
( pattern redactPatterns) {
redacted = redacted.(pattern, );
}
redacted;
}
originalLog = logger..(logger);
logger. = () {
( message === ) {
message = (message);
}
(meta && meta === ) {
meta = .((.(meta)));
}
(level, message, meta);
};
. = 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.();
tracer = trace.();
() {
(...args) => {
span = tracer.(, {
: .,
: {
: operation
}
});
{
result = context.(
trace.(context.(), span),
(...args)
);
span.({ : });
result;
} (error) {
span.({
: ,
: error.
});
span.(error);
span.(, error.);
error;
} {
span.();
}
};
}
. = { 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. === ) ? : ,
: ().(),
: process.(),
checks
});
});
router.(, (req, res) => {
res.(, metrics..);
res.( metrics..());
});
() {
checks = [];
{
db.();
checks.({ : , : });
} (error) {
checks.({ : , : , : error. });
}
{
redis.();
checks.({ : , : });
} (error) {
checks.({ : , : , : error. });
}
memUsage = process.();
heapPercent = (memUsage. / memUsage.) * ;
checks.({
: ,
: heapPercent < ? : ,
: heapPercent.()
});
checks;
}
. = 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:
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"
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.