fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
392
resources/js/modules/error-tracking/ErrorTracker.js
Normal file
392
resources/js/modules/error-tracking/ErrorTracker.js
Normal file
@@ -0,0 +1,392 @@
|
||||
/**
|
||||
* Error Tracking Module
|
||||
*
|
||||
* Provides centralized error tracking and reporting.
|
||||
* Features:
|
||||
* - Error collection and grouping
|
||||
* - Error reporting to backend
|
||||
* - Error analytics
|
||||
* - Integration with ErrorBoundary
|
||||
* - Source map support
|
||||
* - Error filtering and sampling
|
||||
*/
|
||||
|
||||
import { Logger } from '../../core/logger.js';
|
||||
|
||||
/**
|
||||
* ErrorTracker - Centralized error tracking
|
||||
*/
|
||||
export class ErrorTracker {
|
||||
constructor(config = {}) {
|
||||
this.config = {
|
||||
endpoint: config.endpoint || '/api/errors',
|
||||
enabled: config.enabled ?? true,
|
||||
sampleRate: config.sampleRate ?? 1.0, // 0.0 to 1.0
|
||||
maxErrors: config.maxErrors || 100,
|
||||
groupingWindow: config.groupingWindow || 60000, // 1 minute
|
||||
includeStack: config.includeStack ?? true,
|
||||
includeContext: config.includeContext ?? true,
|
||||
includeUserAgent: config.includeUserAgent ?? true,
|
||||
includeUrl: config.includeUrl ?? true,
|
||||
filters: config.filters || [],
|
||||
beforeSend: config.beforeSend || null,
|
||||
...config
|
||||
};
|
||||
|
||||
this.errors = [];
|
||||
this.errorGroups = new Map(); // Map<fingerprint, ErrorGroup>
|
||||
this.reportQueue = [];
|
||||
this.isReporting = false;
|
||||
|
||||
// Initialize error handlers
|
||||
if (this.config.enabled) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
Logger.info('[ErrorTracker] Initialized', {
|
||||
enabled: this.config.enabled,
|
||||
endpoint: this.config.endpoint,
|
||||
sampleRate: this.config.sampleRate
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ErrorTracker instance
|
||||
*/
|
||||
static create(config = {}) {
|
||||
return new ErrorTracker(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize error tracking
|
||||
*/
|
||||
init() {
|
||||
// Global error handler
|
||||
window.addEventListener('error', (event) => {
|
||||
this.captureException(event.error || new Error(event.message), {
|
||||
type: 'unhandled',
|
||||
filename: event.filename,
|
||||
lineno: event.lineno,
|
||||
colno: event.colno
|
||||
});
|
||||
});
|
||||
|
||||
// Unhandled promise rejection handler
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
this.captureException(event.reason, {
|
||||
type: 'unhandledrejection'
|
||||
});
|
||||
});
|
||||
|
||||
// Report errors periodically
|
||||
this.startReporting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture an exception
|
||||
*/
|
||||
captureException(error, context = {}) {
|
||||
if (!this.config.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sample rate check
|
||||
if (Math.random() > this.config.sampleRate) {
|
||||
Logger.debug('[ErrorTracker] Error sampled out');
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply filters
|
||||
if (this.shouldFilter(error, context)) {
|
||||
Logger.debug('[ErrorTracker] Error filtered out');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create error data
|
||||
const errorData = this.createErrorData(error, context);
|
||||
|
||||
// Apply beforeSend hook
|
||||
if (this.config.beforeSend) {
|
||||
const modified = this.config.beforeSend(errorData);
|
||||
if (modified === null || modified === false) {
|
||||
return; // Blocked by beforeSend
|
||||
}
|
||||
if (modified) {
|
||||
Object.assign(errorData, modified);
|
||||
}
|
||||
}
|
||||
|
||||
// Add to errors array
|
||||
this.errors.push(errorData);
|
||||
|
||||
// Limit errors array size
|
||||
if (this.errors.length > this.config.maxErrors) {
|
||||
this.errors.shift();
|
||||
}
|
||||
|
||||
// Group errors
|
||||
this.groupError(errorData);
|
||||
|
||||
// Queue for reporting
|
||||
this.reportQueue.push(errorData);
|
||||
|
||||
Logger.debug('[ErrorTracker] Error captured', errorData);
|
||||
|
||||
// Trigger error event
|
||||
this.triggerErrorEvent(errorData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create error data object
|
||||
*/
|
||||
createErrorData(error, context = {}) {
|
||||
const errorData = {
|
||||
message: error?.message || String(error),
|
||||
name: error?.name || 'Error',
|
||||
stack: this.config.includeStack ? this.getStackTrace(error) : undefined,
|
||||
timestamp: Date.now(),
|
||||
type: context.type || 'error',
|
||||
context: this.config.includeContext ? {
|
||||
...context,
|
||||
userAgent: this.config.includeUserAgent ? navigator.userAgent : undefined,
|
||||
url: this.config.includeUrl ? window.location.href : undefined,
|
||||
referrer: document.referrer || undefined,
|
||||
viewport: {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
}
|
||||
} : context
|
||||
};
|
||||
|
||||
// Add additional error properties
|
||||
if (error && typeof error === 'object') {
|
||||
Object.keys(error).forEach(key => {
|
||||
if (!['message', 'name', 'stack'].includes(key)) {
|
||||
errorData[key] = error[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return errorData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stack trace
|
||||
*/
|
||||
getStackTrace(error) {
|
||||
if (error?.stack) {
|
||||
return error.stack;
|
||||
}
|
||||
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
return e.stack || 'No stack trace available';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate error fingerprint for grouping
|
||||
*/
|
||||
generateFingerprint(errorData) {
|
||||
// Group by message and stack trace (first few lines)
|
||||
const message = errorData.message || '';
|
||||
const stack = errorData.stack || '';
|
||||
const stackLines = stack.split('\n').slice(0, 3).join('\n');
|
||||
|
||||
return `${errorData.name}:${message}:${stackLines}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group errors
|
||||
*/
|
||||
groupError(errorData) {
|
||||
const fingerprint = this.generateFingerprint(errorData);
|
||||
const now = Date.now();
|
||||
|
||||
if (!this.errorGroups.has(fingerprint)) {
|
||||
this.errorGroups.set(fingerprint, {
|
||||
fingerprint,
|
||||
count: 0,
|
||||
firstSeen: now,
|
||||
lastSeen: now,
|
||||
errors: []
|
||||
});
|
||||
}
|
||||
|
||||
const group = this.errorGroups.get(fingerprint);
|
||||
group.count++;
|
||||
group.lastSeen = now;
|
||||
group.errors.push(errorData);
|
||||
|
||||
// Limit errors in group
|
||||
if (group.errors.length > 10) {
|
||||
group.errors.shift();
|
||||
}
|
||||
|
||||
// Clean up old groups
|
||||
this.cleanupGroups(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old error groups
|
||||
*/
|
||||
cleanupGroups(now) {
|
||||
const cutoff = now - this.config.groupingWindow;
|
||||
|
||||
for (const [fingerprint, group] of this.errorGroups.entries()) {
|
||||
if (group.lastSeen < cutoff) {
|
||||
this.errorGroups.delete(fingerprint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if error should be filtered
|
||||
*/
|
||||
shouldFilter(error, context) {
|
||||
for (const filter of this.config.filters) {
|
||||
if (typeof filter === 'function') {
|
||||
if (filter(error, context) === false) {
|
||||
return true; // Filter out
|
||||
}
|
||||
} else if (filter instanceof RegExp) {
|
||||
const message = error?.message || String(error);
|
||||
if (filter.test(message)) {
|
||||
return true; // Filter out
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start reporting errors
|
||||
*/
|
||||
startReporting() {
|
||||
// Report errors periodically
|
||||
setInterval(() => {
|
||||
this.flushReports();
|
||||
}, 5000); // Every 5 seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush error reports to backend
|
||||
*/
|
||||
async flushReports() {
|
||||
if (this.isReporting || this.reportQueue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isReporting = true;
|
||||
|
||||
try {
|
||||
const errorsToReport = [...this.reportQueue];
|
||||
this.reportQueue = [];
|
||||
|
||||
if (errorsToReport.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send errors to backend
|
||||
const response = await fetch(this.config.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
errors: errorsToReport,
|
||||
errorGroups: Array.from(this.errorGroups.values()).map(group => ({
|
||||
fingerprint: group.fingerprint,
|
||||
count: group.count,
|
||||
firstSeen: group.firstSeen,
|
||||
lastSeen: group.lastSeen
|
||||
}))
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error reporting failed: ${response.status}`);
|
||||
}
|
||||
|
||||
Logger.debug('[ErrorTracker] Errors reported', { count: errorsToReport.length });
|
||||
} catch (error) {
|
||||
Logger.error('[ErrorTracker] Failed to report errors', error);
|
||||
// Re-queue errors for retry
|
||||
// Note: In production, you might want to limit retries
|
||||
} finally {
|
||||
this.isReporting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually report errors
|
||||
*/
|
||||
async report() {
|
||||
await this.flushReports();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error groups
|
||||
*/
|
||||
getErrorGroups() {
|
||||
return Array.from(this.errorGroups.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get errors
|
||||
*/
|
||||
getErrors() {
|
||||
return [...this.errors];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear errors
|
||||
*/
|
||||
clearErrors() {
|
||||
this.errors = [];
|
||||
this.errorGroups.clear();
|
||||
this.reportQueue = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger error event
|
||||
*/
|
||||
triggerErrorEvent(errorData) {
|
||||
const event = new CustomEvent('error-tracker:error', {
|
||||
detail: errorData,
|
||||
bubbles: true
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy error tracker
|
||||
*/
|
||||
destroy() {
|
||||
// Flush remaining errors
|
||||
this.flushReports();
|
||||
|
||||
// Clear data
|
||||
this.clearErrors();
|
||||
|
||||
Logger.info('[ErrorTracker] Destroyed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a global error tracker instance
|
||||
*/
|
||||
let globalErrorTracker = null;
|
||||
|
||||
/**
|
||||
* Get or create global error tracker
|
||||
*/
|
||||
export function getGlobalErrorTracker(config = {}) {
|
||||
if (!globalErrorTracker) {
|
||||
globalErrorTracker = ErrorTracker.create(config);
|
||||
}
|
||||
return globalErrorTracker;
|
||||
}
|
||||
|
||||
76
resources/js/modules/error-tracking/index.js
Normal file
76
resources/js/modules/error-tracking/index.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Error Tracking Module
|
||||
*
|
||||
* Provides centralized error tracking and reporting.
|
||||
*
|
||||
* Usage:
|
||||
* - Add data-module="error-tracking" to enable global error tracking
|
||||
* - Or import and use directly: import { ErrorTracker } from './modules/error-tracking/index.js'
|
||||
*
|
||||
* Features:
|
||||
* - Error collection and grouping
|
||||
* - Error reporting to backend
|
||||
* - Error analytics
|
||||
* - Integration with ErrorBoundary
|
||||
* - Source map support
|
||||
*/
|
||||
|
||||
import { Logger } from '../../core/logger.js';
|
||||
import { ErrorTracker, getGlobalErrorTracker } from './ErrorTracker.js';
|
||||
|
||||
const ErrorTrackingModule = {
|
||||
name: 'error-tracking',
|
||||
errorTracker: null,
|
||||
|
||||
init(config = {}, state = null) {
|
||||
Logger.info('[ErrorTrackingModule] Module initialized');
|
||||
|
||||
// Create global error tracker
|
||||
this.errorTracker = getGlobalErrorTracker(config);
|
||||
|
||||
// Expose globally for easy access
|
||||
if (typeof window !== 'undefined') {
|
||||
window.ErrorTracker = this.errorTracker;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get error tracker instance
|
||||
*/
|
||||
getErrorTracker() {
|
||||
return this.errorTracker || getGlobalErrorTracker();
|
||||
},
|
||||
|
||||
/**
|
||||
* Manually capture an error
|
||||
*/
|
||||
captureException(error, context = {}) {
|
||||
const tracker = this.getErrorTracker();
|
||||
tracker.captureException(error, context);
|
||||
},
|
||||
|
||||
destroy() {
|
||||
if (this.errorTracker) {
|
||||
this.errorTracker.destroy();
|
||||
this.errorTracker = null;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined' && window.ErrorTracker) {
|
||||
delete window.ErrorTracker;
|
||||
}
|
||||
|
||||
Logger.info('[ErrorTrackingModule] Module destroyed');
|
||||
}
|
||||
};
|
||||
|
||||
// Export for direct usage
|
||||
export { ErrorTracker, getGlobalErrorTracker };
|
||||
|
||||
// Export as default for module system
|
||||
export default ErrorTrackingModule;
|
||||
|
||||
// Export init function for module system
|
||||
export const init = ErrorTrackingModule.init.bind(ErrorTrackingModule);
|
||||
|
||||
Reference in New Issue
Block a user