142 lines
3.9 KiB
TypeScript
142 lines
3.9 KiB
TypeScript
/**
|
|
* Metrics Plugin
|
|
*
|
|
* Automatically collects metrics for requests and responses
|
|
*/
|
|
|
|
import { defaultMetricsCollector } from '../../observability/metrics.js';
|
|
import type { Plugin, PluginContext, PluginResponse, MetricsPluginConfig } from '../types.js';
|
|
|
|
/**
|
|
* Default configuration
|
|
*/
|
|
const DEFAULT_CONFIG: Required<MetricsPluginConfig> = {
|
|
enabled: true,
|
|
prefix: 'elasticsearch',
|
|
recordDuration: true,
|
|
recordSize: true,
|
|
recordResponseSize: true,
|
|
};
|
|
|
|
/**
|
|
* Create metrics plugin
|
|
*/
|
|
export function createMetricsPlugin(config: MetricsPluginConfig = {}): Plugin {
|
|
const pluginConfig = { ...DEFAULT_CONFIG, ...config };
|
|
const metrics = defaultMetricsCollector;
|
|
|
|
return {
|
|
name: 'metrics',
|
|
version: '1.0.0',
|
|
priority: 20, // Execute early, after logging
|
|
|
|
beforeRequest: (context: PluginContext) => {
|
|
if (!pluginConfig.enabled) {
|
|
return context;
|
|
}
|
|
|
|
// Record request counter
|
|
metrics.recordCounter(`${pluginConfig.prefix}.requests`, 1, {
|
|
method: context.request.method,
|
|
path: extractIndexFromPath(context.request.path),
|
|
});
|
|
|
|
// Record request size if enabled
|
|
if (pluginConfig.recordSize && context.request.body) {
|
|
const size = Buffer.byteLength(JSON.stringify(context.request.body), 'utf8');
|
|
metrics.recordHistogram(`${pluginConfig.prefix}.request.size`, size, {
|
|
method: context.request.method,
|
|
});
|
|
}
|
|
|
|
return context;
|
|
},
|
|
|
|
afterResponse: <T>(context: PluginContext, response: PluginResponse<T>) => {
|
|
if (!pluginConfig.enabled) {
|
|
return response;
|
|
}
|
|
|
|
const duration = Date.now() - context.request.startTime;
|
|
|
|
// Record request duration if enabled
|
|
if (pluginConfig.recordDuration) {
|
|
metrics.recordHistogram(`${pluginConfig.prefix}.request.duration`, duration, {
|
|
method: context.request.method,
|
|
path: extractIndexFromPath(context.request.path),
|
|
status: response.statusCode.toString(),
|
|
});
|
|
}
|
|
|
|
// Record response size if enabled
|
|
if (pluginConfig.recordResponseSize && response.body) {
|
|
const size = Buffer.byteLength(JSON.stringify(response.body), 'utf8');
|
|
metrics.recordHistogram(`${pluginConfig.prefix}.response.size`, size, {
|
|
method: context.request.method,
|
|
status: response.statusCode.toString(),
|
|
});
|
|
}
|
|
|
|
// Record success/failure
|
|
const success = response.statusCode >= 200 && response.statusCode < 300;
|
|
metrics.recordCounter(
|
|
`${pluginConfig.prefix}.requests.${success ? 'success' : 'failure'}`,
|
|
1,
|
|
{
|
|
method: context.request.method,
|
|
status: response.statusCode.toString(),
|
|
}
|
|
);
|
|
|
|
return response;
|
|
},
|
|
|
|
onError: (context) => {
|
|
if (!pluginConfig.enabled) {
|
|
return null;
|
|
}
|
|
|
|
const duration = Date.now() - context.request.startTime;
|
|
|
|
// Record error
|
|
metrics.recordCounter(`${pluginConfig.prefix}.errors`, 1, {
|
|
method: context.request.method,
|
|
path: extractIndexFromPath(context.request.path),
|
|
error: context.error.name,
|
|
});
|
|
|
|
// Record error duration
|
|
if (pluginConfig.recordDuration) {
|
|
metrics.recordHistogram(`${pluginConfig.prefix}.error.duration`, duration, {
|
|
method: context.request.method,
|
|
error: context.error.name,
|
|
});
|
|
}
|
|
|
|
// Don't handle error
|
|
return null;
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Extract index name from path
|
|
*/
|
|
function extractIndexFromPath(path: string): string {
|
|
// Remove leading slash
|
|
const cleanPath = path.startsWith('/') ? path.slice(1) : path;
|
|
|
|
// Split by slash and get first segment
|
|
const segments = cleanPath.split('/');
|
|
|
|
// Common patterns:
|
|
// /{index}/_search
|
|
// /{index}/_doc/{id}
|
|
// /_cat/indices
|
|
if (segments[0].startsWith('_')) {
|
|
return segments[0]; // API endpoint like _cat, _search
|
|
}
|
|
|
|
return segments[0] || 'unknown';
|
|
}
|