BREAKING CHANGE(core): Refactor to v3: introduce modular core/domain architecture, plugin system, observability and strict TypeScript configuration; remove legacy classes
This commit is contained in:
141
ts/core/plugins/built-in/metrics-plugin.ts
Normal file
141
ts/core/plugins/built-in/metrics-plugin.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 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';
|
||||
}
|
||||
Reference in New Issue
Block a user