feat(observability): P3.2-A/B/C Enhanced observability coverage
P3.2-A: Expanded event coverage - Added recovery events (RECOVERY_START, RECOVERY_COMPLETE, RECOVERY_ERROR) - Added database events (DB_QUERY_START, DB_QUERY_COMPLETE, DB_QUERY_ERROR) - Added state transition event (STATE_TRANSITION) - Added background task events (BACKGROUND_TASK_START, COMPLETE, ERROR) P3.2-B: Structured metrics export - Added exportMetrics() method to export all metrics as JSON - Added getMetricsSummary() method for lightweight metrics summary P3.2-C: Improved error context - Added toJSON() method to DailyNotificationError for structured logging - Added logError() method to ObservabilityManager with enhanced error context Verification: - TypeScript compiles ✅ - No new dependencies ✅ - JSON export is valid ✅
This commit is contained in:
@@ -106,6 +106,20 @@ export class DailyNotificationError extends Error {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert error to JSON for structured logging
|
||||
* @returns JSON-serializable error representation
|
||||
*/
|
||||
toJSON(): Record<string, unknown> {
|
||||
return {
|
||||
code: this.code,
|
||||
message: this.message,
|
||||
stack: this.stack,
|
||||
timestamp: Date.now(),
|
||||
...(this.details && { details: this.details }),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create error for missing required parameter
|
||||
*/
|
||||
|
||||
@@ -76,6 +76,20 @@ export const EVENT_CODES = {
|
||||
ANDROID_WORKMANAGER_START: 'DNP-ANDROID-WM-START',
|
||||
IOS_BGTASK_START: 'DNP-IOS-BGTASK-START',
|
||||
ELECTRON_NOTIFICATION: 'DNP-ELECTRON-NOTIFICATION',
|
||||
// Recovery events
|
||||
RECOVERY_START: 'DNP-RECOVERY-START',
|
||||
RECOVERY_COMPLETE: 'DNP-RECOVERY-COMPLETE',
|
||||
RECOVERY_ERROR: 'DNP-RECOVERY-ERROR',
|
||||
// Database events
|
||||
DB_QUERY_START: 'DNP-DB-QUERY-START',
|
||||
DB_QUERY_COMPLETE: 'DNP-DB-QUERY-COMPLETE',
|
||||
DB_QUERY_ERROR: 'DNP-DB-QUERY-ERROR',
|
||||
// State transition events
|
||||
STATE_TRANSITION: 'DNP-STATE-TRANSITION',
|
||||
// Background task events
|
||||
BACKGROUND_TASK_START: 'DNP-BG-TASK-START',
|
||||
BACKGROUND_TASK_COMPLETE: 'DNP-BG-TASK-COMPLETE',
|
||||
BACKGROUND_TASK_ERROR: 'DNP-BG-TASK-ERROR',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
EVENT_CODES,
|
||||
createEventLog,
|
||||
} from './core/events';
|
||||
import { DailyNotificationError } from './core/errors';
|
||||
|
||||
export interface HealthStatus {
|
||||
nextRuns: number[];
|
||||
@@ -134,6 +135,32 @@ export class ObservabilityManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error with enhanced context
|
||||
* @param eventCode Event code
|
||||
* @param message Human-readable message
|
||||
* @param error Error object
|
||||
* @param context Additional context
|
||||
*/
|
||||
logError(eventCode: string, message: string, error: Error, context?: Record<string, unknown>): void {
|
||||
const errorData: Record<string, unknown> = {
|
||||
error: error.message,
|
||||
errorCode: error instanceof DailyNotificationError ? error.code : undefined,
|
||||
errorName: error.name,
|
||||
...(error instanceof DailyNotificationError && error.details ? { errorDetails: error.details } : {}),
|
||||
...(error.stack ? { stack: error.stack } : {}),
|
||||
...context,
|
||||
};
|
||||
|
||||
// Use toJSON if available for structured error data
|
||||
if (error instanceof DailyNotificationError && typeof (error as unknown as { toJSON?: () => Record<string, unknown> }).toJSON === 'function') {
|
||||
const jsonError = (error as unknown as { toJSON: () => Record<string, unknown> }).toJSON();
|
||||
Object.assign(errorData, jsonError);
|
||||
}
|
||||
|
||||
this.logEvent('ERROR', eventCode, message, errorData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record performance metrics
|
||||
*/
|
||||
@@ -204,6 +231,47 @@ export class ObservabilityManager {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Export metrics as JSON
|
||||
* @returns JSON string of all metrics
|
||||
*/
|
||||
exportMetrics(): string {
|
||||
return JSON.stringify({
|
||||
performance: this.performanceMetrics,
|
||||
user: this.userMetrics,
|
||||
platform: this.platformMetrics,
|
||||
events: this.eventLogs.slice(0, 100), // Last 100 events
|
||||
exportedAt: Date.now(),
|
||||
schemaVersion: 1
|
||||
}, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metrics summary (lightweight)
|
||||
* @returns Summary object
|
||||
*/
|
||||
getMetricsSummary(): {
|
||||
eventCount: number;
|
||||
successRate: number;
|
||||
avgFetchTime: number;
|
||||
avgNotifyTime: number;
|
||||
} {
|
||||
const fetchTimes = this.performanceMetrics.fetchTimes;
|
||||
const notifyTimes = this.performanceMetrics.notifyTimes;
|
||||
const total = this.performanceMetrics.successCount + this.performanceMetrics.failureCount;
|
||||
|
||||
return {
|
||||
eventCount: this.eventLogs.length,
|
||||
successRate: total > 0 ? this.performanceMetrics.successCount / total : 0,
|
||||
avgFetchTime: fetchTimes.length > 0
|
||||
? fetchTimes.reduce((a, b) => a + b, 0) / fetchTimes.length
|
||||
: 0,
|
||||
avgNotifyTime: notifyTimes.length > 0
|
||||
? notifyTimes.reduce((a, b) => a + b, 0) / notifyTimes.length
|
||||
: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent event logs
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user