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

- 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:
2025-11-09 14:46:15 +01:00
parent 85c369e846
commit 36ef2a1e2c
1366 changed files with 104925 additions and 28719 deletions

View 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;
}