distributed-trace.ts•17.5 kB
import { z } from 'zod';
import { Logger } from '../server/logger';
import { MetricsCollector } from '../server/metrics';
import { NetMvcAdapter } from '../adapters/netmvc-adapter';
import {
DapDistributedTraceRequest,
DapDistributedTraceResponse,
} from '../schemas/netmvc-tools.schemas';
/**
* dap.trackDistributedTrace tool implementation
*
* Handles distributed tracing for .NET microservices with:
* - Trace correlation and propagation
* - Service communication tracking
* - Performance analysis across service boundaries
* - Visualization of distributed request flows
*/
export class DistributedTraceTool {
private logger: Logger;
private metrics: MetricsCollector;
private adapter: NetMvcAdapter;
constructor() {
this.logger = new Logger('dap.trackDistributedTrace');
this.metrics = MetricsCollector.getInstance();
this.adapter = new NetMvcAdapter();
}
/**
* Execute distributed tracing
*/
async execute(args: any): Promise<DapDistributedTraceResponse> {
this.metrics.startTimer('dap.trackDistributedTrace.tool');
this.metrics.increment('dap.trackDistributedTrace.count');
try {
// Validate input
const validatedArgs = this.validateArgs(args);
this.logger.debug('Tracking distributed trace', {
traceId: validatedArgs.traceId,
spanId: validatedArgs.spanId,
service: validatedArgs.service,
operation: validatedArgs.operation,
});
// Process distributed tracing
const traceResult = await this.trackDistributedTrace(validatedArgs);
this.logger.info('Distributed tracing completed', {
traceId: traceResult.traceId,
totalSpans: traceResult.visualization.spans.length,
totalTime: traceResult.visualization.totalTime,
});
this.metrics.stopTimer('dap.trackDistributedTrace.tool');
return this.createSuccessResponse(traceResult);
} catch (error) {
this.logger.error('Distributed tracing failed:', error);
this.metrics.increment('dap.trackDistributedTrace.error.count');
this.metrics.stopTimer('dap.trackDistributedTrace.tool');
return this.createErrorResponse((error as Error).message);
}
}
/**
* Validate input arguments
*/
private validateArgs(args: any): DapDistributedTraceRequest['arguments'] {
const schema = z.object({
traceId: z.string(),
spanId: z.string(),
parentSpanId: z.string().optional(),
service: z.string(),
operation: z.string(),
startTime: z.number(),
endTime: z.number().optional(),
duration: z.number().optional(),
tags: z.record(z.any()).optional(),
logs: z.array(z.record(z.any())).optional(),
references: z.array(z.record(z.any())).optional(),
baggage: z.record(z.string()).optional(),
});
return schema.parse(args);
}
/**
* Process distributed tracing
*/
private async trackDistributedTrace(args: DapDistributedTraceRequest['arguments']): Promise<any> {
// Generate trace ID if not provided
const traceId =
args.traceId || `trace_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Mock distributed trace processing
const trace = {
traceId,
spans: this.generateSpans(args),
timeline: this.generateTimeline(args),
criticalPath: this.calculateCriticalPath(args),
totalTime: args.duration || this.calculateTotalTime(args),
startTime: args.startTime,
endTime: args.endTime || Date.now(),
// Service communication analysis
serviceAnalysis: {
totalServices: this.countUniqueServices(args),
serviceCalls: this.analyzeServiceCalls(args),
errorRate: this.calculateErrorRate(args),
averageResponseTime: this.calculateAverageResponseTime(args),
},
// Performance metrics
performance: {
totalRequests: this.countTotalRequests(args),
cacheHitRate: Math.random() * 30 + 70, // 70-100%
databaseCalls: this.countDatabaseCalls(args),
externalApiCalls: this.countExternalApiCalls(args),
serializationTime: Math.random() * 50 + 10,
networkTime: Math.random() * 100 + 20,
},
// Trace health
health: {
status: this.determineTraceHealth(args),
warnings: this.generateWarnings(args),
recommendations: this.generateRecommendations(args),
},
// .NET specific tracing
dotnetTracing: {
activitySource: this.generateActivitySource(args),
operationName: this.generateOperationName(args),
tags: this.generateDotnetTags(args),
links: this.generateDotnetLinks(args),
},
};
// Add to adapter tracking
await this.adapter.trackDistributedTrace(trace);
return {
traceId,
visualization: trace,
};
}
/**
* Generate spans for the trace
*/
private generateSpans(args: DapDistributedTraceRequest['arguments']): any[] {
const spans = [];
const baseStartTime = args.startTime;
// Generate service spans
const services = [
'UserService',
'ProductService',
'OrderService',
'PaymentService',
'NotificationService',
];
// let currentSpanId = args.spanId; // Unused
for (let i = 0; i < services.length; i++) {
const service = services[i];
const duration = Math.random() * 200 + 50; // 50-250ms
const startTime = baseStartTime + i * 100; // Stagger start times
const span: any = {
id: `span_${i}_${Date.now()}`,
parentId: i > 0 ? spans[i - 1].id : args.parentSpanId,
operationName: `${service}.${args.operation}`,
startTime,
duration,
service,
tags: {
'service.name': service,
'span.kind': i === 0 ? 'server' : 'client',
'http.method': i === 0 ? 'POST' : 'GET',
'http.status_code': i < services.length - 1 ? 200 : 201,
...args.tags,
},
logs: this.generateSpanLogs(service || 'unknown', duration),
isError: Math.random() < 0.05, // 5% error rate
stackTrace:
i === services.length - 1 ? this.generateStackTrace(service || 'unknown') : undefined,
};
spans.push(span);
}
return spans;
}
/**
* Generate timeline events
*/
private generateTimeline(args: DapDistributedTraceRequest['arguments']): any[] {
const events = [];
const baseTime = args.startTime;
// Service discovery and initialization
events.push({
timestamp: baseTime,
event: 'ServiceDiscovery',
service: 'ServiceRegistry',
operation: 'DiscoverServices',
duration: Math.random() * 20 + 5,
properties: {
services: ['UserService', 'ProductService', 'OrderService'],
discoveryType: 'Consul',
},
});
// Request routing
events.push({
timestamp: baseTime + 50,
event: 'RequestRouting',
service: 'ApiGateway',
operation: 'RouteRequest',
duration: Math.random() * 10 + 2,
properties: {
route: '/api/orders',
targetService: 'OrderService',
correlationId: args.traceId,
},
});
// Service calls
['UserService', 'ProductService', 'OrderService'].forEach((service, index) => {
events.push({
timestamp: baseTime + 100 + index * 50,
event: 'ServiceCall',
service,
operation: args.operation,
duration: Math.random() * 100 + 50,
properties: {
requestId: `req_${Date.now()}_${index}`,
parameters: JSON.stringify({ userId: 123, orderId: 456 }),
responseTime: Math.random() * 200 + 100,
},
});
});
// Database operations
events.push({
timestamp: baseTime + 200,
event: 'DatabaseQuery',
service: 'Database',
operation: 'ExecuteQuery',
duration: Math.random() * 50 + 20,
properties: {
query: 'SELECT * FROM Orders WHERE UserId = @p0',
parameters: { '@p0': 123 },
connection: 'DefaultConnection',
},
});
// Response aggregation
events.push({
timestamp: baseTime + 400,
event: 'ResponseAggregation',
service: 'ApiGateway',
operation: 'AggregateResponses',
duration: Math.random() * 20 + 5,
properties: {
totalResponses: 3,
successCount: 3,
errorCount: 0,
},
});
return events;
}
/**
* Calculate critical path
*/
private calculateCriticalPath(args: DapDistributedTraceRequest['arguments']): string[] {
const criticalPath = [];
const spans = this.generateSpans(args);
// Find the longest path (critical path)
let currentSpan = spans.find(span => !span.parentId);
while (currentSpan) {
criticalPath.push(currentSpan.id);
const childSpans = spans.filter(span => span.parentId === currentSpan.id);
currentSpan = childSpans.length > 0 ? childSpans[0] : null;
}
return criticalPath;
}
/**
* Calculate total time
*/
private calculateTotalTime(args: DapDistributedTraceRequest['arguments']): number {
if (args.duration) {
return args.duration;
}
const spans = this.generateSpans(args);
const startTimes = spans.map(span => span.startTime);
const endTimes = spans.map(span => span.startTime + span.duration);
return Math.max(...endTimes) - Math.min(...startTimes);
}
/**
* Count unique services
*/
private countUniqueServices(_args: DapDistributedTraceRequest['arguments']): number {
const services = [
'UserService',
'ProductService',
'OrderService',
'PaymentService',
'NotificationService',
];
return services.length;
}
/**
* Analyze service calls
*/
private analyzeServiceCalls(_args: DapDistributedTraceRequest['arguments']): any[] {
return [
{
service: 'UserService',
callCount: 1,
averageResponseTime: Math.random() * 50 + 20,
errorRate: 0,
},
{
service: 'ProductService',
callCount: 1,
averageResponseTime: Math.random() * 80 + 30,
errorRate: 0,
},
{
service: 'OrderService',
callCount: 1,
averageResponseTime: Math.random() * 100 + 40,
errorRate: Math.random() < 0.1 ? 10 : 0, // 10% chance of errors
},
];
}
/**
* Calculate error rate
*/
private calculateErrorRate(_args: DapDistributedTraceRequest['arguments']): number {
return Math.random() < 0.1 ? Math.random() * 5 : 0; // 0-5% error rate
}
/**
* Calculate average response time
*/
private calculateAverageResponseTime(_args: DapDistributedTraceRequest['arguments']): number {
return Math.random() * 100 + 50; // 50-150ms
}
/**
* Count total requests
*/
private countTotalRequests(_args: DapDistributedTraceRequest['arguments']): number {
return Math.floor(Math.random() * 100) + 10; // 10-110 requests
}
/**
* Count database calls
*/
private countDatabaseCalls(_args: DapDistributedTraceRequest['arguments']): number {
return Math.floor(Math.random() * 20) + 5; // 5-25 database calls
}
/**
* Count external API calls
*/
private countExternalApiCalls(_args: DapDistributedTraceRequest['arguments']): number {
return Math.floor(Math.random() * 10) + 2; // 2-12 external API calls
}
/**
* Determine trace health
*/
private determineTraceHealth(args: DapDistributedTraceRequest['arguments']): string {
const errorRate = this.calculateErrorRate(args);
const avgResponseTime = this.calculateAverageResponseTime(args);
if (errorRate > 2 || avgResponseTime > 200) {
return 'degraded';
}
return 'healthy';
}
/**
* Generate warnings
*/
private generateWarnings(args: DapDistributedTraceRequest['arguments']): string[] {
const warnings = [];
if (this.calculateErrorRate(args) > 2) {
warnings.push('High error rate detected in service calls');
}
if (this.calculateAverageResponseTime(args) > 150) {
warnings.push('High response times detected');
}
if (this.countDatabaseCalls(args) > 15) {
warnings.push('High number of database calls detected');
}
if (warnings.length === 0) {
warnings.push('No issues detected');
}
return warnings;
}
/**
* Generate recommendations
*/
private generateRecommendations(_args: DapDistributedTraceRequest['arguments']): string[] {
const recommendations = [];
recommendations.push('Consider implementing caching for frequently accessed data');
recommendations.push('Optimize database queries with proper indexing');
recommendations.push('Add circuit breakers for external service calls');
recommendations.push('Implement rate limiting to prevent service overload');
recommendations.push('Consider using async patterns for long-running operations');
return recommendations;
}
/**
* Generate activity source
*/
private generateActivitySource(args: DapDistributedTraceRequest['arguments']): string {
return `${args.service}.${args.operation}`;
}
/**
* Generate operation name
*/
private generateOperationName(args: DapDistributedTraceRequest['arguments']): string {
return `${args.service}.${args.operation}`;
}
/**
* Generate .NET specific tags
*/
private generateDotnetTags(args: DapDistributedTraceRequest['arguments']): Record<string, any> {
return {
'otel.library.name': 'Microsoft.Extensions.Diagnostics',
'otel.library.version': '8.0.0',
'dotnet.component': 'AspNetCore',
'dotnet.runtime.version': '8.0.0',
'span.kind': 'server',
'http.method': args.operation.includes('Get') ? 'GET' : 'POST',
'http.route': `/${args.service.toLowerCase()}/${args.operation.toLowerCase()}`,
};
}
/**
* Generate .NET specific links
*/
private generateDotnetLinks(args: DapDistributedTraceRequest['arguments']): any[] {
return [
{
context: `trace/${args.traceId}`,
attributes: {
'link.type': 'parent',
},
},
];
}
/**
* Generate span logs
*/
private generateSpanLogs(service: string, duration: number): any[] {
const logs = [];
logs.push({
timestamp: Date.now(),
fields: {
event: 'span.start',
service: service,
'duration.start': 0,
},
});
logs.push({
timestamp: Date.now() + duration,
fields: {
event: 'span.end',
service: service,
'duration.end': duration,
status: 'OK',
},
});
return logs;
}
/**
* Generate stack trace
*/
private generateStackTrace(service: string): string {
return `at ${service}.${service}Controller.<>c__DisplayClass0_0.<ExecuteAsync>b__0()\n at System.Threading.Tasks.Task\`1.InnerInvoke()\n at System.Threading.Tasks.Task.ExecuteWithThreadCurrentCulture()`;
}
/**
* Create success response
*/
private createSuccessResponse(body: any): DapDistributedTraceResponse {
return {
type: 'response',
seq: 1,
command: 'trackDistributedTrace',
request_seq: 1,
success: true,
body: {
success: true,
traceId: body.traceId,
message: 'Distributed trace tracked successfully',
distributedTrace: body.visualization,
traceVisualization: this.formatTraceVisualization(body.visualization),
},
};
}
/**
* Format trace visualization
*/
private formatTraceVisualization(visualization: any): string {
const { spans, timeline, criticalPath, totalTime } = visualization;
let output = `Distributed Trace Visualization\n`;
output += `=============================\n`;
output += `Trace ID: ${visualization.traceId}\n`;
output += `Total Time: ${totalTime}ms\n`;
output += `Total Spans: ${spans.length}\n`;
output += `Critical Path: ${criticalPath.join(' -> ')}\n\n`;
output += `Spans:\n`;
output += `------\n`;
spans.forEach((span: any, index: number) => {
output += `${index + 1}. ${span.operationName}\n`;
output += ` Service: ${span.service}\n`;
output += ` Duration: ${span.duration}ms\n`;
output += ` Status: ${span.isError ? 'ERROR' : 'OK'}\n`;
if (span.parentId) {
output += ` Parent: ${span.parentId}\n`;
}
output += `\n`;
});
output += `Timeline:\n`;
output += `---------\n`;
timeline.forEach((event: any, index: number) => {
output += `${index + 1}. ${event.event} at ${event.timestamp}ms\n`;
output += ` Service: ${event.service}\n`;
output += ` Operation: ${event.operation}\n`;
output += ` Duration: ${event.duration}ms\n`;
output += `\n`;
});
return output;
}
/**
* Create error response
*/
private createErrorResponse(message: string): DapDistributedTraceResponse {
return {
type: 'response',
seq: 1,
command: 'trackDistributedTrace',
request_seq: 1,
success: false,
message,
body: {
message: 'Failed to track distributed trace',
success: false,
traceId: 'trace_error_' + Date.now(),
},
};
}
}
// Singleton instance
export const distributedTraceTool = new DistributedTraceTool();
// Tool execution function
export async function executeDistributedTrace(args: any): Promise<DapDistributedTraceResponse> {
return await distributedTraceTool.execute(args);
}