create-fluxstack 1.0.13 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +29 -29
- package/app/client/README.md +69 -69
- package/app/client/index.html +14 -13
- package/app/client/src/App.tsx +157 -524
- package/app/client/src/components/ErrorBoundary.tsx +107 -0
- package/app/client/src/components/ErrorDisplay.css +365 -0
- package/app/client/src/components/ErrorDisplay.tsx +258 -0
- package/app/client/src/components/FluxStackConfig.tsx +1321 -0
- package/app/client/src/components/HybridLiveCounter.tsx +140 -0
- package/app/client/src/components/LiveClock.tsx +286 -0
- package/app/client/src/components/MainLayout.tsx +390 -0
- package/app/client/src/components/SidebarNavigation.tsx +391 -0
- package/app/client/src/components/StateDemo.tsx +178 -0
- package/app/client/src/components/SystemMonitor.tsx +1038 -0
- package/app/client/src/components/Teste.tsx +104 -0
- package/app/client/src/components/UserProfile.tsx +809 -0
- package/app/client/src/hooks/useAuth.ts +39 -0
- package/app/client/src/hooks/useNotifications.ts +56 -0
- package/app/client/src/lib/eden-api.ts +189 -53
- package/app/client/src/lib/errors.ts +340 -0
- package/app/client/src/lib/hooks/useErrorHandler.ts +258 -0
- package/app/client/src/lib/index.ts +45 -0
- package/app/client/src/main.tsx +3 -2
- package/app/client/src/pages/ApiDocs.tsx +182 -0
- package/app/client/src/pages/Demo.tsx +174 -0
- package/app/client/src/pages/HybridLive.tsx +263 -0
- package/app/client/src/pages/Overview.tsx +155 -0
- package/app/client/src/store/README.md +43 -0
- package/app/client/src/store/index.ts +16 -0
- package/app/client/src/store/slices/uiSlice.ts +151 -0
- package/app/client/src/store/slices/userSlice.ts +161 -0
- package/app/client/src/test/README.md +257 -0
- package/app/client/src/test/setup.ts +70 -0
- package/app/client/src/test/types.ts +12 -0
- package/app/client/src/vite-env.d.ts +1 -1
- package/app/client/tsconfig.app.json +44 -43
- package/app/client/tsconfig.json +7 -7
- package/app/client/tsconfig.node.json +25 -25
- package/app/client/zustand-setup.md +65 -0
- package/app/server/controllers/users.controller.ts +68 -68
- package/app/server/index.ts +9 -1
- package/app/server/live/CounterComponent.ts +191 -0
- package/app/server/live/FluxStackConfig.ts +529 -0
- package/app/server/live/LiveClockComponent.ts +214 -0
- package/app/server/live/SidebarNavigation.ts +156 -0
- package/app/server/live/SystemMonitor.ts +594 -0
- package/app/server/live/SystemMonitorIntegration.ts +151 -0
- package/app/server/live/TesteComponent.ts +87 -0
- package/app/server/live/UserProfileComponent.ts +135 -0
- package/app/server/live/register-components.ts +28 -0
- package/app/server/middleware/auth.ts +136 -0
- package/app/server/middleware/errorHandling.ts +250 -0
- package/app/server/middleware/index.ts +10 -0
- package/app/server/middleware/rateLimit.ts +193 -0
- package/app/server/middleware/requestLogging.ts +215 -0
- package/app/server/middleware/validation.ts +270 -0
- package/app/server/routes/index.ts +14 -2
- package/app/server/routes/upload.ts +92 -0
- package/app/server/routes/users.routes.ts +2 -9
- package/app/server/services/NotificationService.ts +302 -0
- package/app/server/services/UserService.ts +222 -0
- package/app/server/services/index.ts +46 -0
- package/core/cli/commands/plugin-deps.ts +263 -0
- package/core/cli/generators/README.md +339 -0
- package/core/cli/generators/component.ts +770 -0
- package/core/cli/generators/controller.ts +299 -0
- package/core/cli/generators/index.ts +144 -0
- package/core/cli/generators/interactive.ts +228 -0
- package/core/cli/generators/prompts.ts +83 -0
- package/core/cli/generators/route.ts +513 -0
- package/core/cli/generators/service.ts +465 -0
- package/core/cli/generators/template-engine.ts +154 -0
- package/core/cli/generators/types.ts +71 -0
- package/core/cli/generators/utils.ts +192 -0
- package/core/cli/index.ts +69 -0
- package/core/cli/plugin-discovery.ts +16 -85
- package/core/client/fluxstack.ts +17 -0
- package/core/client/hooks/index.ts +7 -0
- package/core/client/hooks/state-validator.ts +130 -0
- package/core/client/hooks/useAuth.ts +49 -0
- package/core/client/hooks/useChunkedUpload.ts +258 -0
- package/core/client/hooks/useHybridLiveComponent.ts +967 -0
- package/core/client/hooks/useWebSocket.ts +373 -0
- package/core/client/index.ts +47 -0
- package/core/client/state/createStore.ts +193 -0
- package/core/client/state/index.ts +15 -0
- package/core/config/env-dynamic.ts +1 -1
- package/core/config/env.ts +2 -1
- package/core/config/runtime-config.ts +3 -3
- package/core/config/schema.ts +84 -49
- package/core/framework/server.ts +30 -0
- package/core/index.ts +25 -0
- package/core/live/ComponentRegistry.ts +399 -0
- package/core/live/types.ts +164 -0
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +1201 -0
- package/core/plugins/built-in/live-components/index.ts +27 -0
- package/core/plugins/built-in/logger/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +1 -1
- package/core/plugins/built-in/static/index.ts +1 -1
- package/core/plugins/built-in/swagger/index.ts +1 -1
- package/core/plugins/built-in/vite/index.ts +1 -1
- package/core/plugins/dependency-manager.ts +384 -0
- package/core/plugins/index.ts +5 -1
- package/core/plugins/manager.ts +7 -3
- package/core/plugins/registry.ts +88 -10
- package/core/plugins/types.ts +11 -11
- package/core/server/framework.ts +43 -0
- package/core/server/index.ts +11 -1
- package/core/server/live/ComponentRegistry.ts +1017 -0
- package/core/server/live/FileUploadManager.ts +272 -0
- package/core/server/live/LiveComponentPerformanceMonitor.ts +930 -0
- package/core/server/live/SingleConnectionManager.ts +0 -0
- package/core/server/live/StateSignature.ts +644 -0
- package/core/server/live/WebSocketConnectionManager.ts +688 -0
- package/core/server/live/websocket-plugin.ts +435 -0
- package/core/server/middleware/errorHandling.ts +141 -0
- package/core/server/middleware/index.ts +16 -0
- package/core/server/plugins/static-files-plugin.ts +232 -0
- package/core/server/services/BaseService.ts +95 -0
- package/core/server/services/ServiceContainer.ts +144 -0
- package/core/server/services/index.ts +9 -0
- package/core/templates/create-project.ts +46 -2
- package/core/testing/index.ts +10 -0
- package/core/testing/setup.ts +74 -0
- package/core/types/build.ts +38 -14
- package/core/types/types.ts +319 -0
- package/core/utils/env-runtime.ts +7 -0
- package/core/utils/errors/handlers.ts +264 -39
- package/core/utils/errors/index.ts +528 -18
- package/core/utils/errors/middleware.ts +114 -0
- package/core/utils/logger/formatters.ts +222 -0
- package/core/utils/logger/index.ts +167 -48
- package/core/utils/logger/middleware.ts +253 -0
- package/core/utils/logger/performance.ts +384 -0
- package/core/utils/logger/transports.ts +365 -0
- package/create-fluxstack.ts +296 -296
- package/fluxstack.config.ts +17 -1
- package/package-template.json +66 -66
- package/package.json +31 -6
- package/public/README.md +16 -0
- package/vite.config.ts +29 -14
- package/.claude/settings.local.json +0 -74
- package/.github/workflows/ci-build-tests.yml +0 -480
- package/.github/workflows/dependency-management.yml +0 -324
- package/.github/workflows/release-validation.yml +0 -355
- package/.kiro/specs/fluxstack-architecture-optimization/design.md +0 -700
- package/.kiro/specs/fluxstack-architecture-optimization/requirements.md +0 -127
- package/.kiro/specs/fluxstack-architecture-optimization/tasks.md +0 -330
- package/CLAUDE.md +0 -200
- package/Dockerfile +0 -58
- package/Dockerfile.backend +0 -52
- package/Dockerfile.frontend +0 -54
- package/README-Docker.md +0 -85
- package/ai-context/00-QUICK-START.md +0 -86
- package/ai-context/README.md +0 -88
- package/ai-context/development/eden-treaty-guide.md +0 -362
- package/ai-context/development/patterns.md +0 -382
- package/ai-context/development/plugins-guide.md +0 -572
- package/ai-context/examples/crud-complete.md +0 -626
- package/ai-context/project/architecture.md +0 -399
- package/ai-context/project/overview.md +0 -213
- package/ai-context/recent-changes/eden-treaty-refactor.md +0 -281
- package/ai-context/recent-changes/type-inference-fix.md +0 -223
- package/ai-context/reference/environment-vars.md +0 -384
- package/ai-context/reference/troubleshooting.md +0 -407
- package/app/client/src/components/TestPage.tsx +0 -453
- package/bun.lock +0 -1063
- package/bunfig.toml +0 -16
- package/core/__tests__/integration.test.ts +0 -227
- package/core/build/index.ts +0 -186
- package/core/config/__tests__/config-loader.test.ts +0 -554
- package/core/config/__tests__/config-merger.test.ts +0 -657
- package/core/config/__tests__/env-converter.test.ts +0 -372
- package/core/config/__tests__/env-processor.test.ts +0 -431
- package/core/config/__tests__/env.test.ts +0 -452
- package/core/config/__tests__/integration.test.ts +0 -418
- package/core/config/__tests__/loader.test.ts +0 -331
- package/core/config/__tests__/schema.test.ts +0 -129
- package/core/config/__tests__/validator.test.ts +0 -318
- package/core/framework/__tests__/server.test.ts +0 -233
- package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
- package/core/plugins/__tests__/manager.test.ts +0 -398
- package/core/plugins/__tests__/monitoring.test.ts +0 -401
- package/core/plugins/__tests__/registry.test.ts +0 -335
- package/core/utils/__tests__/errors.test.ts +0 -139
- package/core/utils/__tests__/helpers.test.ts +0 -297
- package/core/utils/__tests__/logger.test.ts +0 -141
- package/create-test-app.ts +0 -156
- package/docker-compose.microservices.yml +0 -75
- package/docker-compose.simple.yml +0 -57
- package/docker-compose.yml +0 -71
- package/eslint.config.js +0 -23
- package/flux-cli.ts +0 -214
- package/nginx-lb.conf +0 -37
- package/publish.sh +0 -63
- package/run-clean.ts +0 -26
- package/run-env-tests.ts +0 -313
- package/tailwind.config.js +0 -34
- package/tests/__mocks__/api.ts +0 -56
- package/tests/fixtures/users.ts +0 -69
- package/tests/integration/api/users.routes.test.ts +0 -221
- package/tests/setup.ts +0 -29
- package/tests/unit/app/client/App-simple.test.tsx +0 -56
- package/tests/unit/app/client/App.test.tsx.skip +0 -237
- package/tests/unit/app/client/eden-api.test.ts +0 -186
- package/tests/unit/app/client/simple.test.tsx +0 -23
- package/tests/unit/app/controllers/users.controller.test.ts +0 -150
- package/tests/unit/core/create-project.test.ts.skip +0 -95
- package/tests/unit/core/framework.test.ts +0 -144
- package/tests/unit/core/plugins/logger.test.ts.skip +0 -268
- package/tests/unit/core/plugins/vite.test.ts.disabled +0 -188
- package/tests/utils/test-helpers.ts +0 -61
- package/vitest.config.ts +0 -50
- package/workspace.json +0 -6
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Elysia } from 'elysia'
|
|
2
|
+
import { EnhancedErrorHandler, type ErrorHandlerContext, type ErrorHandlerOptions, type ErrorMetricsCollector } from './handlers'
|
|
3
|
+
import type { Logger } from '../logger/index'
|
|
4
|
+
|
|
5
|
+
export interface ErrorMiddlewareOptions extends ErrorHandlerOptions {
|
|
6
|
+
logger?: Logger
|
|
7
|
+
isDevelopment?: boolean
|
|
8
|
+
enableRequestContext?: boolean
|
|
9
|
+
metricsCollector?: ErrorMetricsCollector
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const errorMiddleware = (options: ErrorMiddlewareOptions = {}) => {
|
|
13
|
+
const handler = new EnhancedErrorHandler(options)
|
|
14
|
+
|
|
15
|
+
return new Elysia({ name: 'error-handler' })
|
|
16
|
+
.onError(async ({ error, request, path, set }) => {
|
|
17
|
+
// Extract request context
|
|
18
|
+
const context: ErrorHandlerContext = {
|
|
19
|
+
logger: options.logger || console as any, // Fallback to console if no logger provided
|
|
20
|
+
isDevelopment: options.isDevelopment ?? process.env.NODE_ENV === 'development',
|
|
21
|
+
request,
|
|
22
|
+
path,
|
|
23
|
+
method: request.method,
|
|
24
|
+
correlationId: request.headers.get('x-correlation-id') || undefined,
|
|
25
|
+
userId: request.headers.get('x-user-id') || undefined,
|
|
26
|
+
userAgent: request.headers.get('user-agent') || undefined,
|
|
27
|
+
ip: request.headers.get('x-forwarded-for') ||
|
|
28
|
+
request.headers.get('x-real-ip') ||
|
|
29
|
+
'unknown',
|
|
30
|
+
metricsCollector: options.metricsCollector
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Convert Elysia error to standard Error if needed
|
|
35
|
+
const standardError = error instanceof Error ? error : new Error(String(error))
|
|
36
|
+
const errorResponse = await handler.handle(standardError, context)
|
|
37
|
+
|
|
38
|
+
// Set response status code
|
|
39
|
+
set.status = errorResponse.error.statusCode
|
|
40
|
+
|
|
41
|
+
// Set correlation ID header if available
|
|
42
|
+
if (errorResponse.error.correlationId) {
|
|
43
|
+
set.headers['x-correlation-id'] = errorResponse.error.correlationId
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return errorResponse
|
|
47
|
+
} catch (handlerError) {
|
|
48
|
+
// Fallback error handling if the error handler itself fails
|
|
49
|
+
const fallbackLogger = options.logger || console as any
|
|
50
|
+
fallbackLogger.error('Error handler failed', {
|
|
51
|
+
originalError: error instanceof Error ? error.message : String(error),
|
|
52
|
+
handlerError: handlerError instanceof Error ? handlerError.message : handlerError
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
set.status = 500
|
|
56
|
+
return {
|
|
57
|
+
error: {
|
|
58
|
+
message: 'Internal server error',
|
|
59
|
+
code: 'INTERNAL_ERROR',
|
|
60
|
+
statusCode: 500,
|
|
61
|
+
timestamp: new Date().toISOString()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Correlation ID middleware to add correlation IDs to requests
|
|
69
|
+
export const correlationIdMiddleware = () => {
|
|
70
|
+
return new Elysia({ name: 'correlation-id' })
|
|
71
|
+
.onRequest(({ request, set }) => {
|
|
72
|
+
// Check if correlation ID already exists in headers
|
|
73
|
+
let correlationId = request.headers.get('x-correlation-id')
|
|
74
|
+
|
|
75
|
+
// Generate new correlation ID if not present
|
|
76
|
+
if (!correlationId) {
|
|
77
|
+
correlationId = crypto.randomUUID()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Add correlation ID to response headers
|
|
81
|
+
set.headers['x-correlation-id'] = correlationId
|
|
82
|
+
|
|
83
|
+
// Store correlation ID in request context for later use
|
|
84
|
+
// Note: This would typically be stored in a request-scoped context
|
|
85
|
+
// For now, we'll rely on the error handler to extract it from headers
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Request context middleware to extract and store request information
|
|
90
|
+
export const requestContextMiddleware = () => {
|
|
91
|
+
return new Elysia({ name: 'request-context' })
|
|
92
|
+
.onRequest(({ request, set }) => {
|
|
93
|
+
// Extract useful request information and store in headers for error handling
|
|
94
|
+
const userAgent = request.headers.get('user-agent')
|
|
95
|
+
const ip = request.headers.get('x-forwarded-for') ||
|
|
96
|
+
request.headers.get('x-real-ip') ||
|
|
97
|
+
'unknown'
|
|
98
|
+
|
|
99
|
+
// Store in custom headers for error handler access
|
|
100
|
+
// In a real implementation, this would use request-scoped storage
|
|
101
|
+
if (userAgent) {
|
|
102
|
+
set.headers['x-internal-user-agent'] = userAgent
|
|
103
|
+
}
|
|
104
|
+
set.headers['x-internal-ip'] = ip
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Combined error handling middleware with all features
|
|
109
|
+
export const fullErrorHandlingMiddleware = (options: ErrorMiddlewareOptions = {}) => {
|
|
110
|
+
return new Elysia({ name: 'full-error-handling' })
|
|
111
|
+
.use(correlationIdMiddleware())
|
|
112
|
+
.use(requestContextMiddleware())
|
|
113
|
+
.use(errorMiddleware(options))
|
|
114
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FluxStack Logger Formatters
|
|
3
|
+
* Different formatting strategies for log output
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { LogEntry, LogLevel } from './transports'
|
|
7
|
+
|
|
8
|
+
export interface LogFormatter {
|
|
9
|
+
format(entry: LogEntry): string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Pretty formatter for development with colors and readable layout
|
|
14
|
+
*/
|
|
15
|
+
export class PrettyFormatter implements LogFormatter {
|
|
16
|
+
private colors: boolean
|
|
17
|
+
private timestamp: boolean
|
|
18
|
+
|
|
19
|
+
private colorMap = {
|
|
20
|
+
debug: '\x1b[36m', // cyan
|
|
21
|
+
info: '\x1b[32m', // green
|
|
22
|
+
warn: '\x1b[33m', // yellow
|
|
23
|
+
error: '\x1b[31m', // red
|
|
24
|
+
reset: '\x1b[0m',
|
|
25
|
+
gray: '\x1b[90m'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
constructor(options: { colors?: boolean; timestamp?: boolean } = {}) {
|
|
29
|
+
this.colors = options.colors !== false
|
|
30
|
+
this.timestamp = options.timestamp !== false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
format(entry: LogEntry): string {
|
|
34
|
+
const { timestamp, level, message, meta, context } = entry
|
|
35
|
+
|
|
36
|
+
let formatted = ''
|
|
37
|
+
|
|
38
|
+
// Add timestamp
|
|
39
|
+
if (this.timestamp) {
|
|
40
|
+
const color = this.colors ? this.colorMap.gray : ''
|
|
41
|
+
const reset = this.colors ? this.colorMap.reset : ''
|
|
42
|
+
formatted += `${color}[${timestamp}]${reset} `
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Add level with color and icon
|
|
46
|
+
const levelColor = this.colors ? this.colorMap[level] : ''
|
|
47
|
+
const reset = this.colors ? this.colorMap.reset : ''
|
|
48
|
+
const levelIcon = this.getLevelIcon(level)
|
|
49
|
+
const levelStr = level.toUpperCase().padEnd(5)
|
|
50
|
+
formatted += `${levelColor}${levelIcon} ${levelStr}${reset} `
|
|
51
|
+
|
|
52
|
+
// Add context if available
|
|
53
|
+
if (context && Object.keys(context).length > 0) {
|
|
54
|
+
const contextStr = Object.entries(context)
|
|
55
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
56
|
+
.join(' ')
|
|
57
|
+
const contextColor = this.colors ? this.colorMap.gray : ''
|
|
58
|
+
formatted += `${contextColor}[${contextStr}]${reset} `
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Add message
|
|
62
|
+
formatted += message
|
|
63
|
+
|
|
64
|
+
// Add meta if available
|
|
65
|
+
if (meta && typeof meta === 'object') {
|
|
66
|
+
const metaColor = this.colors ? this.colorMap.gray : ''
|
|
67
|
+
const metaStr = this.formatMeta(meta)
|
|
68
|
+
formatted += ` ${metaColor}${metaStr}${reset}`
|
|
69
|
+
} else if (meta !== undefined) {
|
|
70
|
+
formatted += ` ${meta}`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return formatted
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private getLevelIcon(level: LogLevel): string {
|
|
77
|
+
switch (level) {
|
|
78
|
+
case 'debug': return '🔍'
|
|
79
|
+
case 'info': return 'ℹ️ '
|
|
80
|
+
case 'warn': return '⚠️ '
|
|
81
|
+
case 'error': return '❌'
|
|
82
|
+
default: return ' '
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private formatMeta(meta: any): string {
|
|
87
|
+
if (typeof meta === 'object' && meta !== null) {
|
|
88
|
+
// Pretty print objects with indentation
|
|
89
|
+
return JSON.stringify(meta, null, 2)
|
|
90
|
+
.split('\n')
|
|
91
|
+
.map((line, index) => index === 0 ? line : ` ${line}`)
|
|
92
|
+
.join('\n')
|
|
93
|
+
}
|
|
94
|
+
return String(meta)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* JSON formatter for production with structured output
|
|
100
|
+
*/
|
|
101
|
+
export class JSONFormatter implements LogFormatter {
|
|
102
|
+
private pretty: boolean
|
|
103
|
+
|
|
104
|
+
constructor(options: { pretty?: boolean } = {}) {
|
|
105
|
+
this.pretty = options.pretty || false
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
format(entry: LogEntry): string {
|
|
109
|
+
const jsonEntry = {
|
|
110
|
+
'@timestamp': entry.timestamp,
|
|
111
|
+
level: entry.level,
|
|
112
|
+
message: entry.message,
|
|
113
|
+
...(entry.context && { context: entry.context }),
|
|
114
|
+
...(entry.meta && { meta: entry.meta })
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return this.pretty
|
|
118
|
+
? JSON.stringify(jsonEntry, null, 2)
|
|
119
|
+
: JSON.stringify(jsonEntry)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Simple formatter for basic text output
|
|
125
|
+
*/
|
|
126
|
+
export class SimpleFormatter implements LogFormatter {
|
|
127
|
+
private timestamp: boolean
|
|
128
|
+
|
|
129
|
+
constructor(options: { timestamp?: boolean } = {}) {
|
|
130
|
+
this.timestamp = options.timestamp !== false
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
format(entry: LogEntry): string {
|
|
134
|
+
const { timestamp, level, message, meta, context } = entry
|
|
135
|
+
|
|
136
|
+
let formatted = ''
|
|
137
|
+
|
|
138
|
+
// Add timestamp
|
|
139
|
+
if (this.timestamp) {
|
|
140
|
+
formatted += `[${timestamp}] `
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Add level
|
|
144
|
+
formatted += `${level.toUpperCase().padEnd(5)} `
|
|
145
|
+
|
|
146
|
+
// Add context if available
|
|
147
|
+
if (context && Object.keys(context).length > 0) {
|
|
148
|
+
const contextStr = Object.entries(context)
|
|
149
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
150
|
+
.join(' ')
|
|
151
|
+
formatted += `[${contextStr}] `
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Add message
|
|
155
|
+
formatted += message
|
|
156
|
+
|
|
157
|
+
// Add meta if available
|
|
158
|
+
if (meta && typeof meta === 'object') {
|
|
159
|
+
formatted += ` ${JSON.stringify(meta)}`
|
|
160
|
+
} else if (meta !== undefined) {
|
|
161
|
+
formatted += ` ${meta}`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return formatted
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Syslog formatter for system logging
|
|
170
|
+
*/
|
|
171
|
+
export class SyslogFormatter implements LogFormatter {
|
|
172
|
+
private facility: number
|
|
173
|
+
private hostname: string
|
|
174
|
+
|
|
175
|
+
constructor(options: { facility?: number; hostname?: string } = {}) {
|
|
176
|
+
this.facility = options.facility || 16 // local0
|
|
177
|
+
this.hostname = options.hostname || require('os').hostname()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
format(entry: LogEntry): string {
|
|
181
|
+
const { timestamp, level, message, meta, context } = entry
|
|
182
|
+
|
|
183
|
+
// Calculate priority (facility * 8 + severity)
|
|
184
|
+
const severity = this.getLevelSeverity(level)
|
|
185
|
+
const priority = this.facility * 8 + severity
|
|
186
|
+
|
|
187
|
+
// Format timestamp in RFC3339
|
|
188
|
+
const syslogTime = new Date(timestamp).toISOString()
|
|
189
|
+
|
|
190
|
+
// Build syslog message
|
|
191
|
+
let syslogMessage = `<${priority}>${syslogTime} ${this.hostname} fluxstack: `
|
|
192
|
+
|
|
193
|
+
// Add context if available
|
|
194
|
+
if (context && Object.keys(context).length > 0) {
|
|
195
|
+
const contextStr = Object.entries(context)
|
|
196
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
197
|
+
.join(' ')
|
|
198
|
+
syslogMessage += `[${contextStr}] `
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
syslogMessage += message
|
|
202
|
+
|
|
203
|
+
// Add meta if available
|
|
204
|
+
if (meta && typeof meta === 'object') {
|
|
205
|
+
syslogMessage += ` ${JSON.stringify(meta)}`
|
|
206
|
+
} else if (meta !== undefined) {
|
|
207
|
+
syslogMessage += ` ${meta}`
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return syslogMessage
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private getLevelSeverity(level: LogLevel): number {
|
|
214
|
+
switch (level) {
|
|
215
|
+
case 'debug': return 7 // debug
|
|
216
|
+
case 'info': return 6 // info
|
|
217
|
+
case 'warn': return 4 // warning
|
|
218
|
+
case 'error': return 3 // error
|
|
219
|
+
default: return 6
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FluxStack Logger
|
|
3
|
-
*
|
|
3
|
+
* Enhanced logging system with multiple transports and formatters
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { ConsoleTransport, FileTransport, JSONTransport } from './transports'
|
|
7
|
+
import { PrettyFormatter, JSONFormatter, SimpleFormatter } from './formatters'
|
|
8
|
+
import type { LogTransport, LogEntry, LogLevel } from './transports'
|
|
9
|
+
import type { LogFormatter } from './formatters'
|
|
7
10
|
|
|
8
|
-
type LogLevel
|
|
11
|
+
export type { LogLevel, LogTransport, LogEntry } from './transports'
|
|
12
|
+
export type { LogFormatter } from './formatters'
|
|
9
13
|
|
|
10
14
|
export interface Logger {
|
|
11
15
|
debug(message: string, meta?: any): void
|
|
@@ -22,6 +26,19 @@ export interface Logger {
|
|
|
22
26
|
|
|
23
27
|
// Request logging
|
|
24
28
|
request(method: string, path: string, status?: number, duration?: number): void
|
|
29
|
+
|
|
30
|
+
// Transport management
|
|
31
|
+
addTransport(transport: LogTransport): void
|
|
32
|
+
removeTransport(name: string): void
|
|
33
|
+
|
|
34
|
+
// Cleanup
|
|
35
|
+
close(): Promise<void>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface LoggerConfig {
|
|
39
|
+
level?: LogLevel
|
|
40
|
+
transports?: LogTransport[]
|
|
41
|
+
defaultMeta?: any
|
|
25
42
|
}
|
|
26
43
|
|
|
27
44
|
class FluxStackLogger implements Logger {
|
|
@@ -29,20 +46,59 @@ class FluxStackLogger implements Logger {
|
|
|
29
46
|
private logLevel: LogLevel
|
|
30
47
|
private context: any = {}
|
|
31
48
|
private timers: Map<string, number> = new Map()
|
|
49
|
+
private transports: Map<string, LogTransport> = new Map()
|
|
50
|
+
private defaultMeta: any = {}
|
|
32
51
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
52
|
+
constructor(config?: LoggerConfig) {
|
|
53
|
+
this.logLevel = config?.level || (process.env.LOG_LEVEL as LogLevel) || 'info'
|
|
54
|
+
this.context = {}
|
|
55
|
+
this.defaultMeta = config?.defaultMeta || {}
|
|
56
|
+
|
|
57
|
+
// Setup default transports if none provided
|
|
58
|
+
if (config?.transports) {
|
|
59
|
+
config.transports.forEach(transport => {
|
|
60
|
+
this.transports.set(transport.name, transport)
|
|
61
|
+
})
|
|
62
|
+
} else {
|
|
63
|
+
this.setupDefaultTransports()
|
|
64
|
+
}
|
|
37
65
|
}
|
|
38
66
|
|
|
39
|
-
static getInstance(): FluxStackLogger {
|
|
67
|
+
static getInstance(config?: LoggerConfig): FluxStackLogger {
|
|
40
68
|
if (FluxStackLogger.instance === null) {
|
|
41
|
-
FluxStackLogger.instance = new FluxStackLogger()
|
|
69
|
+
FluxStackLogger.instance = new FluxStackLogger(config)
|
|
42
70
|
}
|
|
43
71
|
return FluxStackLogger.instance
|
|
44
72
|
}
|
|
45
73
|
|
|
74
|
+
private setupDefaultTransports(): void {
|
|
75
|
+
const isDevelopment = process.env.NODE_ENV !== 'production'
|
|
76
|
+
|
|
77
|
+
if (isDevelopment) {
|
|
78
|
+
// Development: Pretty console output
|
|
79
|
+
this.transports.set('console', new ConsoleTransport({
|
|
80
|
+
level: this.logLevel,
|
|
81
|
+
colors: true,
|
|
82
|
+
timestamp: true
|
|
83
|
+
}))
|
|
84
|
+
} else {
|
|
85
|
+
// Production: JSON output for structured logging
|
|
86
|
+
this.transports.set('json', new JSONTransport({
|
|
87
|
+
level: this.logLevel,
|
|
88
|
+
pretty: false
|
|
89
|
+
}))
|
|
90
|
+
|
|
91
|
+
// Also add file transport for production
|
|
92
|
+
this.transports.set('file', new FileTransport({
|
|
93
|
+
level: this.logLevel,
|
|
94
|
+
filename: 'logs/fluxstack.log',
|
|
95
|
+
maxSize: 10 * 1024 * 1024, // 10MB
|
|
96
|
+
maxFiles: 5,
|
|
97
|
+
compress: true
|
|
98
|
+
}))
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
46
102
|
private shouldLog(level: LogLevel): boolean {
|
|
47
103
|
const levels: Record<LogLevel, number> = {
|
|
48
104
|
debug: 0,
|
|
@@ -54,58 +110,63 @@ class FluxStackLogger implements Logger {
|
|
|
54
110
|
return levels[level] >= levels[this.logLevel]
|
|
55
111
|
}
|
|
56
112
|
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.map(([key, value]) => `${key}=${value}`)
|
|
67
|
-
.join(' ')
|
|
68
|
-
formatted += ` [${contextStr}]`
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
formatted += ` ${message}`
|
|
72
|
-
|
|
73
|
-
if (meta && typeof meta === 'object') {
|
|
74
|
-
formatted += ` ${JSON.stringify(meta)}`
|
|
75
|
-
} else if (meta !== undefined) {
|
|
76
|
-
formatted += ` ${meta}`
|
|
113
|
+
private async writeToTransports(level: LogLevel, message: string, meta?: any): Promise<void> {
|
|
114
|
+
if (!this.shouldLog(level)) return
|
|
115
|
+
|
|
116
|
+
const entry: LogEntry = {
|
|
117
|
+
timestamp: new Date().toISOString(),
|
|
118
|
+
level,
|
|
119
|
+
message,
|
|
120
|
+
meta: { ...this.defaultMeta, ...meta },
|
|
121
|
+
context: Object.keys(this.context).length > 0 ? this.context : undefined
|
|
77
122
|
}
|
|
78
|
-
|
|
79
|
-
|
|
123
|
+
|
|
124
|
+
// Write to all transports
|
|
125
|
+
const writePromises = Array.from(this.transports.values()).map(async transport => {
|
|
126
|
+
try {
|
|
127
|
+
await transport.write(entry)
|
|
128
|
+
} catch (error) {
|
|
129
|
+
// Fallback to console if transport fails
|
|
130
|
+
console.error(`Transport ${transport.name} failed:`, error)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
await Promise.all(writePromises)
|
|
80
135
|
}
|
|
81
136
|
|
|
82
137
|
debug(message: string, meta?: any): void {
|
|
83
|
-
|
|
84
|
-
console.
|
|
85
|
-
}
|
|
138
|
+
this.writeToTransports('debug', message, meta).catch(err => {
|
|
139
|
+
console.error('Logger error:', err)
|
|
140
|
+
})
|
|
86
141
|
}
|
|
87
142
|
|
|
88
143
|
info(message: string, meta?: any): void {
|
|
89
|
-
|
|
90
|
-
console.
|
|
91
|
-
}
|
|
144
|
+
this.writeToTransports('info', message, meta).catch(err => {
|
|
145
|
+
console.error('Logger error:', err)
|
|
146
|
+
})
|
|
92
147
|
}
|
|
93
148
|
|
|
94
149
|
warn(message: string, meta?: any): void {
|
|
95
|
-
|
|
96
|
-
console.
|
|
97
|
-
}
|
|
150
|
+
this.writeToTransports('warn', message, meta).catch(err => {
|
|
151
|
+
console.error('Logger error:', err)
|
|
152
|
+
})
|
|
98
153
|
}
|
|
99
154
|
|
|
100
155
|
error(message: string, meta?: any): void {
|
|
101
|
-
|
|
102
|
-
console.error(
|
|
103
|
-
}
|
|
156
|
+
this.writeToTransports('error', message, meta).catch(err => {
|
|
157
|
+
console.error('Logger error:', err)
|
|
158
|
+
})
|
|
104
159
|
}
|
|
105
160
|
|
|
106
161
|
// Contextual logging
|
|
107
162
|
child(context: any): FluxStackLogger {
|
|
108
|
-
|
|
163
|
+
const childLogger = new FluxStackLogger({
|
|
164
|
+
level: this.logLevel,
|
|
165
|
+
transports: Array.from(this.transports.values()),
|
|
166
|
+
defaultMeta: this.defaultMeta
|
|
167
|
+
})
|
|
168
|
+
childLogger.context = { ...this.context, ...context }
|
|
169
|
+
return childLogger
|
|
109
170
|
}
|
|
110
171
|
|
|
111
172
|
// Performance logging
|
|
@@ -124,22 +185,72 @@ class FluxStackLogger implements Logger {
|
|
|
124
185
|
|
|
125
186
|
// HTTP request logging
|
|
126
187
|
request(method: string, path: string, status?: number, duration?: number): void {
|
|
188
|
+
const meta: any = { method, path }
|
|
189
|
+
if (status) meta.status = status
|
|
190
|
+
if (duration) meta.duration = duration
|
|
191
|
+
|
|
127
192
|
const statusStr = status ? ` ${status}` : ''
|
|
128
193
|
const durationStr = duration ? ` (${duration}ms)` : ''
|
|
129
|
-
this.info(`${method} ${path}${statusStr}${durationStr}
|
|
194
|
+
this.info(`${method} ${path}${statusStr}${durationStr}`, meta)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Transport management
|
|
198
|
+
addTransport(transport: LogTransport): void {
|
|
199
|
+
this.transports.set(transport.name, transport)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
removeTransport(name: string): void {
|
|
203
|
+
const transport = this.transports.get(name)
|
|
204
|
+
if (transport && transport.close) {
|
|
205
|
+
const closeResult = transport.close()
|
|
206
|
+
if (closeResult instanceof Promise) {
|
|
207
|
+
closeResult.catch(console.error)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
this.transports.delete(name)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Cleanup
|
|
214
|
+
async close(): Promise<void> {
|
|
215
|
+
const closePromises = Array.from(this.transports.values())
|
|
216
|
+
.filter(transport => transport.close)
|
|
217
|
+
.map(transport => transport.close!())
|
|
218
|
+
|
|
219
|
+
await Promise.all(closePromises)
|
|
220
|
+
this.transports.clear()
|
|
130
221
|
}
|
|
131
222
|
|
|
132
223
|
// Plugin logging
|
|
133
224
|
plugin(pluginName: string, message: string, meta?: any): void {
|
|
134
|
-
this.debug(`[${pluginName}] ${message}`, meta)
|
|
225
|
+
this.debug(`[${pluginName}] ${message}`, { plugin: pluginName, ...meta })
|
|
135
226
|
}
|
|
136
227
|
|
|
137
228
|
// Framework logging
|
|
138
229
|
framework(message: string, meta?: any): void {
|
|
139
|
-
this.info(`[FluxStack] ${message}`, meta)
|
|
230
|
+
this.info(`[FluxStack] ${message}`, { component: 'framework', ...meta })
|
|
140
231
|
}
|
|
141
232
|
}
|
|
142
233
|
|
|
234
|
+
// Export transport and formatter classes
|
|
235
|
+
export { ConsoleTransport, FileTransport, JSONTransport } from './transports'
|
|
236
|
+
export { PrettyFormatter, JSONFormatter, SimpleFormatter } from './formatters'
|
|
237
|
+
|
|
238
|
+
// Export performance utilities
|
|
239
|
+
export {
|
|
240
|
+
RequestLogger,
|
|
241
|
+
PerformanceLogger,
|
|
242
|
+
createRequestLoggingMiddleware
|
|
243
|
+
} from './performance'
|
|
244
|
+
export type { RequestContext, PerformanceTimer } from './performance'
|
|
245
|
+
|
|
246
|
+
// Export middleware utilities
|
|
247
|
+
export {
|
|
248
|
+
createElysiaLoggerMiddleware,
|
|
249
|
+
createDatabaseLoggerMiddleware,
|
|
250
|
+
createPluginLoggerMiddleware,
|
|
251
|
+
createBuildLoggerMiddleware
|
|
252
|
+
} from './middleware'
|
|
253
|
+
|
|
143
254
|
// Export singleton instance
|
|
144
255
|
export const logger = FluxStackLogger.getInstance()
|
|
145
256
|
|
|
@@ -157,5 +268,13 @@ export const log = {
|
|
|
157
268
|
logger.framework(message, meta),
|
|
158
269
|
child: (context: any) => logger.child(context),
|
|
159
270
|
time: (label: string) => logger.time(label),
|
|
160
|
-
timeEnd: (label: string) => logger.timeEnd(label)
|
|
271
|
+
timeEnd: (label: string) => logger.timeEnd(label),
|
|
272
|
+
addTransport: (transport: LogTransport) => logger.addTransport(transport),
|
|
273
|
+
removeTransport: (name: string) => logger.removeTransport(name),
|
|
274
|
+
close: () => logger.close()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Factory function for creating configured loggers
|
|
278
|
+
export function createLogger(config: LoggerConfig): Logger {
|
|
279
|
+
return new FluxStackLogger(config)
|
|
161
280
|
}
|