@ordojs/cli
Version:
Command-line interface for OrdoJS framework
651 lines (594 loc) • 16 kB
JavaScript
/**
* @fileoverview OrdoJS CLI - Monitoring and observability generator
*/
import { CLIError, ErrorType } from '../index.js';
/**
* Monitoring and observability generator for OrdoJS applications
*/
export class MonitoringGenerator {
defaultConfig = {
structuredLogging: true,
logLevel: 'info',
metricsCollection: true,
distributedTracing: true,
errorReporting: true,
healthChecks: true,
performanceMonitoring: true,
alerting: true,
integrations: {
datadog: false,
newrelic: false,
sentry: false,
prometheus: true,
grafana: true
}
};
/**
* Generate monitoring configuration
*/
generateMonitoringConfig(config, options = {}) {
const monitoringConfig = this.mergeConfig(options);
try {
return {
logger: this.generateLoggerConfig(monitoringConfig),
metrics: this.generateMetricsConfig(monitoringConfig),
tracing: this.generateTracingConfig(monitoringConfig),
healthCheck: this.generateHealthCheckConfig(monitoringConfig),
errorHandler: this.generateErrorHandlerConfig(monitoringConfig),
dockerCompose: this.generateDockerComposeMonitoring(monitoringConfig),
kubernetes: this.generateKubernetesMonitoring(monitoringConfig)
};
}
catch (error) {
throw new CLIError(`Failed to generate monitoring configuration: ${error instanceof Error ? error.message : String(error)}`, ErrorType.DEPLOYMENT, 'MONITORING-001');
}
}
/**
* Generate structured logging configuration
*/
generateLoggerConfig(config) {
return `import winston from 'winston';
import { format } from 'winston';
// Configure structured logging
const logger = winston.createLogger({
level: '${config.logLevel}',
format: format.combine(
format.timestamp(),
format.errors({ stack: true }),
format.json()
),
defaultMeta: { service: 'ordojs-app' },
transports: [
new winston.transports.Console({
format: format.combine(
format.colorize(),
format.simple()
)
}),
new winston.transports.File({
filename: 'logs/error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'logs/combined.log'
})
]
});
// Add request logging middleware
export const requestLogger = (req: any, res: any, next: any) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info('HTTP Request', {
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration,
userAgent: req.get('User-Agent'),
ip: req.ip
});
});
next();
};
export default logger;
`;
}
/**
* Generate metrics collection configuration
*/
generateMetricsConfig(config) {
return `import prometheus from 'prom-client';
import { register, collectDefaultMetrics } from 'prom-client';
// Initialize Prometheus metrics
collectDefaultMetrics({ register });
// Custom metrics
export const httpRequestDuration = new prometheus.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.1, 0.5, 1, 2, 5]
});
export const httpRequestTotal = new prometheus.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
export const activeConnections = new prometheus.Gauge({
name: 'active_connections',
help: 'Number of active connections'
});
export const memoryUsage = new prometheus.Gauge({
name: 'memory_usage_bytes',
help: 'Memory usage in bytes',
labelNames: ['type']
});
// Metrics middleware
export const metricsMiddleware = (req: any, res: any, next: any) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
const labels = {
method: req.method,
route: req.route?.path || req.path,
status_code: res.statusCode
};
httpRequestDuration.observe(labels, duration);
httpRequestTotal.inc(labels);
});
next();
};
// Metrics endpoint
export const metricsEndpoint = async (req: any, res: any) => {
try {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
} catch (error) {
res.status(500).end(error);
}
};
// Memory usage monitoring
setInterval(() => {
const memUsage = process.memoryUsage();
memoryUsage.set({ type: 'rss' }, memUsage.rss);
memoryUsage.set({ type: 'heapTotal' }, memUsage.heapTotal);
memoryUsage.set({ type: 'heapUsed' }, memUsage.heapUsed);
memoryUsage.set({ type: 'external' }, memUsage.external);
}, 30000);
`;
}
/**
* Generate distributed tracing configuration
*/
generateTracingConfig(config) {
return `import { trace, context, SpanStatusCode } from '@opentelemetry/api';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
// Initialize tracing
const provider = new NodeTracerProvider();
const exporter = new JaegerExporter({
endpoint: process.env.JAEGER_ENDPOINT || 'http://localhost:14268/api/traces'
});
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
provider.register();
registerInstrumentations({
instrumentations: [getNodeAutoInstrumentations()]
});
const tracer = trace.getTracer('ordojs-app');
// Tracing middleware
export const tracingMiddleware = (req: any, res: any, next: any) => {
const span = tracer.startSpan('HTTP Request', {
attributes: {
'http.method': req.method,
'http.url': req.url,
'http.user_agent': req.get('User-Agent'),
'http.client_ip': req.ip
}
});
const ctx = trace.setSpan(context.active(), span);
context.with(ctx, () => {
res.on('finish', () => {
span.setAttributes({
'http.status_code': res.statusCode,
'http.status_text': res.statusMessage
});
if (res.statusCode >= 400) {
span.setStatus({ code: SpanStatusCode.ERROR });
} else {
span.setStatus({ code: SpanStatusCode.OK });
}
span.end();
});
next();
});
};
// Custom span creation
export const createSpan = (name: string, attributes?: Record<string, any>) => {
return tracer.startSpan(name, { attributes });
};
export { tracer };
`;
}
/**
* Generate health check configuration
*/
generateHealthCheckConfig(config) {
return `import { createHealthCheck } from './health-check.js';
// Health check configuration
const healthChecks = {
database: async () => {
try {
// Add your database health check logic here
return { status: 'healthy', details: 'Database connection OK' };
} catch (error) {
return { status: 'unhealthy', details: error.message };
}
},
redis: async () => {
try {
// Add your Redis health check logic here
return { status: 'healthy', details: 'Redis connection OK' };
} catch (error) {
return { status: 'unhealthy', details: error.message };
}
},
external: async () => {
try {
// Add external service health check logic here
return { status: 'healthy', details: 'External services OK' };
} catch (error) {
return { status: 'unhealthy', details: error.message };
}
}
};
export const healthCheckMiddleware = createHealthCheck(healthChecks);
// Health check endpoint
export const healthCheckEndpoint = async (req: any, res: any) => {
const results = await healthCheckMiddleware(req, res);
const isHealthy = Object.values(results).every(
(result: any) => result.status === 'healthy'
);
res.status(isHealthy ? 200 : 503).json({
status: isHealthy ? 'healthy' : 'unhealthy',
timestamp: new Date().toISOString(),
checks: results
});
};
`;
}
/**
* Generate error handler configuration
*/
generateErrorHandlerConfig(config) {
return `import logger from './logger.js';
import { tracer } from './tracing.js';
// Global error handler
export const errorHandler = (error: any, req: any, res: any, next: any) => {
const span = tracer.startSpan('Error Handler');
// Log error with structured logging
logger.error('Application Error', {
error: {
message: error.message,
stack: error.stack,
name: error.name
},
request: {
method: req.method,
url: req.url,
headers: req.headers,
body: req.body,
userAgent: req.get('User-Agent'),
ip: req.ip
},
user: req.user?.id,
timestamp: new Date().toISOString()
});
// Set span attributes
span.setAttributes({
'error.message': error.message,
'error.type': error.name,
'error.stack': error.stack
});
span.setStatus({ code: 2 }); // ERROR
span.end();
// Send error response
const statusCode = error.statusCode || 500;
const message = statusCode === 500 ? 'Internal Server Error' : error.message;
res.status(statusCode).json({
error: {
message,
statusCode,
timestamp: new Date().toISOString()
}
});
};
// Async error wrapper
export const asyncHandler = (fn: Function) => {
return (req: any, res: any, next: any) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
// Unhandled rejection handler
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled Rejection', {
reason: reason instanceof Error ? reason.message : reason,
stack: reason instanceof Error ? reason.stack : undefined,
promise: promise.toString()
});
});
// Uncaught exception handler
process.on('uncaughtException', (error) => {
logger.error('Uncaught Exception', {
error: {
message: error.message,
stack: error.stack,
name: error.name
}
});
// Graceful shutdown
process.exit(1);
});
`;
}
/**
* Generate Docker Compose monitoring services
*/
generateDockerComposeMonitoring(config) {
return `# Monitoring services for Docker Compose
version: '3.8'
services:
# Prometheus for metrics collection
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--storage.tsdb.retention.time=200h'
- '--web.enable-lifecycle'
restart: unless-stopped
# Grafana for visualization
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3001:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/grafana/provisioning:/etc/grafana/provisioning
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
restart: unless-stopped
# Jaeger for distributed tracing
jaeger:
image: jaegertracing/all-in-one:latest
container_name: jaeger
ports:
- "16686:16686"
- "14268:14268"
environment:
- COLLECTOR_OTLP_ENABLED=true
restart: unless-stopped
# Redis for caching and session storage
redis:
image: redis:alpine
container_name: redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
prometheus_data:
grafana_data:
redis_data:
`;
}
/**
* Generate Kubernetes monitoring manifests
*/
generateKubernetesMonitoring(config) {
return `# Prometheus ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
data:
prometheus.yml: |
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'ordojs-app'
static_configs:
- targets: ['ordojs-app-service:3000']
metrics_path: '/metrics'
scrape_interval: 5s
# Prometheus Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
spec:
replicas: 1
selector:
matchLabels:
app: prometheus
template:
metadata:
labels:
app: prometheus
spec:
containers:
- name: prometheus
image: prom/prometheus:latest
ports:
- containerPort: 9090
volumeMounts:
- name: prometheus-config
mountPath: /etc/prometheus
- name: prometheus-storage
mountPath: /prometheus
command:
- /bin/prometheus
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --web.console.libraries=/etc/prometheus/console_libraries
- --web.console.templates=/etc/prometheus/consoles
- --storage.tsdb.retention.time=200h
- --web.enable-lifecycle
volumes:
- name: prometheus-config
configMap:
name: prometheus-config
- name: prometheus-storage
persistentVolumeClaim:
claimName: prometheus-pvc
# Prometheus Service
apiVersion: v1
kind: Service
metadata:
name: prometheus-service
spec:
selector:
app: prometheus
ports:
- port: 9090
targetPort: 9090
type: ClusterIP
# Grafana Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: grafana
spec:
replicas: 1
selector:
matchLabels:
app: grafana
template:
metadata:
labels:
app: grafana
spec:
containers:
- name: grafana
image: grafana/grafana:latest
ports:
- containerPort: 3000
env:
- name: GF_SECURITY_ADMIN_PASSWORD
value: "admin"
- name: GF_USERS_ALLOW_SIGN_UP
value: "false"
volumeMounts:
- name: grafana-storage
mountPath: /var/lib/grafana
volumes:
- name: grafana-storage
persistentVolumeClaim:
claimName: grafana-pvc
# Grafana Service
apiVersion: v1
kind: Service
metadata:
name: grafana-service
spec:
selector:
app: grafana
ports:
- port: 3000
targetPort: 3000
type: ClusterIP
# Jaeger Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: jaeger
spec:
replicas: 1
selector:
matchLabels:
app: jaeger
template:
metadata:
labels:
app: jaeger
spec:
containers:
- name: jaeger
image: jaegertracing/all-in-one:latest
ports:
- containerPort: 16686
- containerPort: 14268
env:
- name: COLLECTOR_OTLP_ENABLED
value: "true"
# Jaeger Service
apiVersion: v1
kind: Service
metadata:
name: jaeger-service
spec:
selector:
app: jaeger
ports:
- name: ui
port: 16686
targetPort: 16686
- name: collector
port: 14268
targetPort: 14268
type: ClusterIP
# Persistent Volume Claims
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prometheus-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: grafana-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
`;
}
mergeConfig(options) {
return {
...this.defaultConfig,
...options,
integrations: {
...this.defaultConfig.integrations,
...options.integrations
}
};
}
}
//# sourceMappingURL=monitoring-generator.js.map