project setup with core files including configuration, package management, and basic structure. Added .gitignore, README, and various TypeScript types for CMS components. Implemented initial components and layouts for the application.
This commit is contained in:
112
middlelayer/monitoring/README.md
Normal file
112
middlelayer/monitoring/README.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Monitoring & Observability
|
||||
|
||||
Der Middlelayer ist mit umfassendem Monitoring ausgestattet:
|
||||
|
||||
## 1. Structured Logging (Winston)
|
||||
|
||||
**Konfiguration:**
|
||||
- Log-Level: `LOG_LEVEL` (default: `info`)
|
||||
- Format: JSON in Production, farbig in Development
|
||||
- Output: Console + Files (`logs/error.log`, `logs/combined.log`)
|
||||
|
||||
**Verwendung:**
|
||||
```typescript
|
||||
import { logger, logQuery, logError } from './monitoring/logger.js';
|
||||
|
||||
logger.info('Info message', { context: 'data' });
|
||||
logQuery('GetProducts', { limit: 10 }, 45);
|
||||
logError(error, { operation: 'getProducts' });
|
||||
```
|
||||
|
||||
## 2. Prometheus Metrics
|
||||
|
||||
**Endpoints:**
|
||||
- `GET http://localhost:9090/metrics` - Prometheus Metrics
|
||||
- `GET http://localhost:9090/health` - Health Check
|
||||
|
||||
**Verfügbare Metriken:**
|
||||
|
||||
### Query Metrics
|
||||
- `graphql_queries_total` - Anzahl der Queries (Labels: `operation`, `status`)
|
||||
- `graphql_query_duration_seconds` - Query-Dauer (Histogram)
|
||||
- `graphql_query_complexity` - Query-Komplexität (Gauge)
|
||||
|
||||
### Cache Metrics
|
||||
- `cache_hits_total` - Cache Hits (Label: `cache_type`)
|
||||
- `cache_misses_total` - Cache Misses (Label: `cache_type`)
|
||||
|
||||
### DataService Metrics
|
||||
- `dataservice_calls_total` - DataService Aufrufe (Labels: `method`, `status`)
|
||||
- `dataservice_duration_seconds` - DataService Dauer (Histogram)
|
||||
|
||||
### Error Metrics
|
||||
- `errors_total` - Anzahl der Fehler (Labels: `type`, `operation`)
|
||||
|
||||
**Beispiel Prometheus Query:**
|
||||
```promql
|
||||
# Query Rate
|
||||
rate(graphql_queries_total[5m])
|
||||
|
||||
# Error Rate
|
||||
rate(errors_total[5m])
|
||||
|
||||
# Cache Hit Ratio
|
||||
rate(cache_hits_total[5m]) / (rate(cache_hits_total[5m]) + rate(cache_misses_total[5m]))
|
||||
```
|
||||
|
||||
## 3. Distributed Tracing
|
||||
|
||||
**Features:**
|
||||
- Automatische Trace-ID-Generierung pro Request
|
||||
- Span-Tracking für verschachtelte Operationen
|
||||
- Dauer-Messung für Performance-Analyse
|
||||
|
||||
**Trace-IDs werden automatisch in Logs und Metrics eingebunden.**
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
# Logging
|
||||
LOG_LEVEL=info # debug, info, warn, error
|
||||
|
||||
# Metrics
|
||||
METRICS_PORT=9090 # Port für Metrics-Endpoint
|
||||
|
||||
# Query Complexity
|
||||
MAX_QUERY_COMPLEXITY=1000 # Max. Query-Komplexität
|
||||
```
|
||||
|
||||
## Integration mit Grafana
|
||||
|
||||
**Prometheus Scrape Config:**
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'graphql-middlelayer'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
```
|
||||
|
||||
**Grafana Dashboard:**
|
||||
- Importiere die Metriken in Grafana
|
||||
- Erstelle Dashboards für:
|
||||
- Query Performance
|
||||
- Cache Hit Rates
|
||||
- Error Rates
|
||||
- Request Throughput
|
||||
|
||||
## Beispiel-Dashboard Queries
|
||||
|
||||
```promql
|
||||
# Requests pro Sekunde
|
||||
sum(rate(graphql_queries_total[1m])) by (operation)
|
||||
|
||||
# Durchschnittliche Query-Dauer
|
||||
avg(graphql_query_duration_seconds) by (operation)
|
||||
|
||||
# Cache Hit Rate
|
||||
sum(rate(cache_hits_total[5m])) / (sum(rate(cache_hits_total[5m])) + sum(rate(cache_misses_total[5m])))
|
||||
|
||||
# Error Rate
|
||||
sum(rate(errors_total[5m])) by (type, operation)
|
||||
```
|
||||
|
||||
86
middlelayer/monitoring/logger.ts
Normal file
86
middlelayer/monitoring/logger.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import winston from "winston";
|
||||
|
||||
/**
|
||||
* Structured Logging mit Winston
|
||||
* Erstellt JSON-Logs für bessere Analyse und Monitoring
|
||||
*/
|
||||
export 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: "graphql-middlelayer",
|
||||
environment: process.env.NODE_ENV || "development",
|
||||
},
|
||||
transports: [
|
||||
// Console Output (für Development)
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.printf(({ timestamp, level, message, ...meta }) => {
|
||||
const metaStr = Object.keys(meta).length
|
||||
? JSON.stringify(meta, null, 2)
|
||||
: "";
|
||||
return `${timestamp} [${level}]: ${message} ${metaStr}`;
|
||||
})
|
||||
),
|
||||
}),
|
||||
// File Output (für Production)
|
||||
...(process.env.NODE_ENV === "production"
|
||||
? [
|
||||
new winston.transports.File({
|
||||
filename: "logs/error.log",
|
||||
level: "error",
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: "logs/combined.log",
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
});
|
||||
|
||||
// Helper-Funktionen für strukturiertes Logging
|
||||
export const logQuery = (
|
||||
operationName: string,
|
||||
variables: any,
|
||||
duration: number
|
||||
) => {
|
||||
logger.info("GraphQL Query executed", {
|
||||
operation: operationName,
|
||||
variables,
|
||||
duration: `${duration}ms`,
|
||||
type: "query",
|
||||
});
|
||||
};
|
||||
|
||||
export const logError = (error: Error, context?: Record<string, any>) => {
|
||||
logger.error("Error occurred", {
|
||||
error: {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
name: error.name,
|
||||
},
|
||||
...context,
|
||||
type: "error",
|
||||
});
|
||||
};
|
||||
|
||||
export const logCacheHit = (key: string, type: string) => {
|
||||
logger.debug("Cache hit", {
|
||||
cacheKey: key,
|
||||
cacheType: type,
|
||||
type: "cache",
|
||||
});
|
||||
};
|
||||
|
||||
export const logCacheMiss = (key: string, type: string) => {
|
||||
logger.debug("Cache miss", {
|
||||
cacheKey: key,
|
||||
cacheType: type,
|
||||
type: "cache",
|
||||
});
|
||||
};
|
||||
90
middlelayer/monitoring/metrics.ts
Normal file
90
middlelayer/monitoring/metrics.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Registry, Counter, Histogram, Gauge } from 'prom-client';
|
||||
|
||||
/**
|
||||
* Prometheus Metrics Registry
|
||||
* Sammelt Metriken für Monitoring und Alerting
|
||||
*/
|
||||
export const register = new Registry();
|
||||
|
||||
// Default Metrics (CPU, Memory, etc.)
|
||||
register.setDefaultLabels({
|
||||
app: 'graphql-middlelayer',
|
||||
});
|
||||
|
||||
// Query Metrics
|
||||
export const queryCounter = new Counter({
|
||||
name: 'graphql_queries_total',
|
||||
help: 'Total number of GraphQL queries',
|
||||
labelNames: ['operation', 'status'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const queryDuration = new Histogram({
|
||||
name: 'graphql_query_duration_seconds',
|
||||
help: 'Duration of GraphQL queries in seconds',
|
||||
labelNames: ['operation'],
|
||||
buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// Cache Metrics
|
||||
export const cacheHits = new Counter({
|
||||
name: 'cache_hits_total',
|
||||
help: 'Total number of cache hits',
|
||||
labelNames: ['cache_type'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const cacheMisses = new Counter({
|
||||
name: 'cache_misses_total',
|
||||
help: 'Total number of cache misses',
|
||||
labelNames: ['cache_type'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// DataService Metrics
|
||||
export const dataServiceCalls = new Counter({
|
||||
name: 'dataservice_calls_total',
|
||||
help: 'Total number of DataService calls',
|
||||
labelNames: ['method', 'status'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const dataServiceDuration = new Histogram({
|
||||
name: 'dataservice_duration_seconds',
|
||||
help: 'Duration of DataService calls in seconds',
|
||||
labelNames: ['method'],
|
||||
buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// Error Metrics
|
||||
export const errorCounter = new Counter({
|
||||
name: 'errors_total',
|
||||
help: 'Total number of errors',
|
||||
labelNames: ['type', 'operation'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// Active Connections
|
||||
export const activeConnections = new Gauge({
|
||||
name: 'active_connections',
|
||||
help: 'Number of active connections',
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// Query Complexity
|
||||
export const queryComplexityGauge = new Gauge({
|
||||
name: 'graphql_query_complexity',
|
||||
help: 'Complexity of GraphQL queries',
|
||||
labelNames: ['operation'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
/**
|
||||
* Exportiert Metriken im Prometheus-Format
|
||||
*/
|
||||
export async function getMetrics(): Promise<string> {
|
||||
return register.metrics();
|
||||
}
|
||||
|
||||
78
middlelayer/monitoring/tracing.ts
Normal file
78
middlelayer/monitoring/tracing.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Einfaches Distributed Tracing
|
||||
* Erstellt Trace-IDs für Request-Tracking
|
||||
*/
|
||||
|
||||
interface TraceContext {
|
||||
traceId: string;
|
||||
spanId: string;
|
||||
parentSpanId?: string;
|
||||
startTime: number;
|
||||
}
|
||||
|
||||
const traces = new Map<string, TraceContext>();
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Trace
|
||||
*/
|
||||
export function createTrace(traceId?: string): TraceContext {
|
||||
const id = traceId || generateTraceId();
|
||||
const trace: TraceContext = {
|
||||
traceId: id,
|
||||
spanId: generateSpanId(),
|
||||
startTime: Date.now(),
|
||||
};
|
||||
traces.set(id, trace);
|
||||
return trace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen Child-Span
|
||||
*/
|
||||
export function createSpan(traceId: string, parentSpanId?: string): string {
|
||||
const trace = traces.get(traceId);
|
||||
if (!trace) {
|
||||
throw new Error(`Trace ${traceId} not found`);
|
||||
}
|
||||
|
||||
const spanId = generateSpanId();
|
||||
trace.parentSpanId = parentSpanId || trace.spanId;
|
||||
|
||||
return spanId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Beendet einen Trace und gibt die Dauer zurück
|
||||
*/
|
||||
export function endTrace(traceId: string): number {
|
||||
const trace = traces.get(traceId);
|
||||
if (!trace) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const duration = Date.now() - trace.startTime;
|
||||
traces.delete(traceId);
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert eine Trace-ID
|
||||
*/
|
||||
function generateTraceId(): string {
|
||||
return `trace-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert eine Span-ID
|
||||
*/
|
||||
function generateSpanId(): string {
|
||||
return `span-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt Trace-Informationen
|
||||
*/
|
||||
export function getTrace(traceId: string): TraceContext | undefined {
|
||||
return traces.get(traceId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user