observability best practices - smart-village-solutions/sva-studio GitHub Wiki
Dieses Dokument beschreibt Best Practices für Logging, Metriken und Tracing im sva-studio Projekt.
Architektur-Referenz: Logging Architecture
-
Nutze immer den SDK Logger, nicht
console.log - Strukturiertes Logging: Key-Value Pairs statt Textkonkatenation
-
Workspace Context: Jedes Log sollte
workspace_idbeinhalten - PII-Sicherheit: Sensible Daten gehören in Message Body, nicht in Labels
-
Keine Token-URLs: Redirect- oder Logout-URLs mit
id_token_hint,codeoder anderen Geheimnissen werden nie als Rohwert geloggt - Development-Modell: Console und Dev-Konsole sind lokal aktiv; OTEL ist in Development ein zusätzlicher Exportpfad nur bei erfolgreicher Initialisierung
import { createSdkLogger } from '@sva/sdk';
const logger = createSdkLogger({ component: 'auth-service' });
// ✅ GOOD: Strukturierte Logs mit Labels
logger.info('User authenticated successfully', {
workspace_id: 'org-123',
userId: 'user-456',
method: 'oauth2'
});
// ✅ GOOD: Fehler mit Context
logger.error('Database connection failed', {
workspace_id: 'org-123',
component: 'database',
code: 'ECONNREFUSED',
retries: 3
});Vorteile:
- Automatische Redaction von PII (email, token, api_key, etc.)
- Automatische Maskierung von JWT-aehnlichen Strings und sensitiven URL-Query-Parametern
- OTEL Integration (logs + metrics + traces)
- Workspace-Kontext automatisch injiziert
- Konsistente Log-Struktur für Aggregation
// ❌ BAD: Direkter String mit PII
console.log(`User ${email} signed in from IP ${ip}`);
// → Email und IP könnten sichtbar sein!
// ❌ BAD: Tokenhaltige Logout-URL
logger.info('Logout successful', {
redirect_target: 'https://issuer.example/logout?id_token_hint=...',
});
// → Tokenhaltige URLs gehoeren nicht ins operative Logging
// ❌ BAD: Keine Struktur
console.log('Auth completed');
// → Nicht filterbar, kein Context, schwer zu durchsuchen
// ❌ BAD: PII in Labels
logger.info('User login', {
email: '[email protected]', // ← Diese Labels werden gefilmt!
password: 'secret123' // ← NIE!
});- In Development ist die Server-Console immer aktiv.
- Die React-App rendert eine lokale Dev-Konsole fuer Browser- und Server-Logs.
- Die Server-Schnittstelle fuer diese Dev-Konsole liefert ausserhalb von Development bewusst keine Eintraege.
- OTEL darf in Development ausfallen oder explizit deaktiviert werden, ohne den App-Start zu blockieren.
- In Production ist dieses Verhalten nicht zulaessig; dort ist OTEL verpflichtend.
Routing-Ereignisse folgen einem eigenen Safe-Vertrag:
- Guard-Denials, Plugin-Guard-Anomalien und serverseitige Dispatch-Fehler duerfen strukturiert geloggt werden.
- Erfolgreiche Navigationen und Search-Param-Fallbacks ohne Diagnosewert bleiben still.
-
routemeint immer den Template-Pfad, nie den konkret aufgeloesten URL-Pfad mit IDs.
import type { RoutingDiagnosticsHook } from '@sva/routing';
const diagnostics: RoutingDiagnosticsHook = (event) => {
if (event.event === 'routing.guard.access_denied') {
logger.info('Routing guard denied access', event);
}
};// ❌ BAD: aufgeloester Pfad mit IDs und Query-String
logger.warn('Routing denied', {
route: '/admin/users/123?token=secret',
});Das System redacted automatisch sensible Labels auf mehreren Ebenen:
// Diese Labels werden automatisch redacted:
const forbiddenLabels = [
'user_id', 'session_id', 'email', 'request_id',
'token', 'authorization', 'api_key', 'secret',
'ip', 'password', 'card', 'credit', 'ssn'
];
// Diese Labels sind ERLAUBT (Whitelist):
const allowedLabels = [
'workspace_id', // ← Mandatory!
'component', // Service/Module Name
'environment', // dev/test/prod
'level' // info/error/warn/debug
];Email-Masking bei Logs:
[email protected] → j***@example.com
Zusaetzliche Schutzregeln:
- JWT-aehnliche Strings werden maskiert
-
id_token_hint,access_token,refresh_tokenundcodewerden in URLs redacted - lokale Dev-Konsole, Console und OTEL folgen derselben Redaction
Promtail scrapet Docker Container Logs und entfernt weitere PII-Labels:
# dev/monitoring/promtail/promtail-config.yml
relabel_configs:
- action: labeldrop
regex: "(user_id|session_id|email|request_id|...)"
- action: labelkeep
regex: "(workspace_id|component|environment|level)"In Loki landen nur die whitelisted Labels:
{workspace_id="org-123", component="auth", environment="production", level="info"} → message
Der Workspace Context wird automatisch injiziert und sollte immer vorhanden sein:
// packages/auth-runtime/runtime-routes.ts
import { createWorkspaceContextMiddleware } from '@sva/sdk';
app.use(createWorkspaceContextMiddleware({
headerNames: ['x-workspace-id', 'x-sva-workspace-id'],
environment: 'production'
}));
// Jetzt ist workspace_id in AsyncLocalStorage verfügbarimport { getWorkspaceContext, setWorkspaceContext } from '@sva/sdk';
export const deleteUserAccount = async (userId: string) => {
const { workspaceId } = getWorkspaceContext();
logger.info('User account deletion initiated', {
workspace_id: workspaceId, // ← Automatisch injiziert!
userId,
action: 'account_deletion'
});
// ... Logik ...
};Für geschäftskritische Events nutze die Business Event Counter:
import { recordBusinessEvent } from '@sva/monitoring-client';
// Content publikation
recordBusinessEvent('content.published', {
workspace_id: 'org-123',
component: 'cms',
content_type: 'article',
environment: 'production'
});
// User-Aktion
recordBusinessEvent('user.signed_up', {
workspace_id: 'org-123',
component: 'auth',
signup_method: 'oauth2'
});Metrik in Prometheus:
sva_business_events_total{workspace_id="org-123", event="content.published"}
# Publikationen pro Workspace (letzte 24h)
sum(rate(sva_business_events_total{event="content.published"}[24h])) by (workspace_id)
# Sign-ups im Zeitverlauf
increase(sva_business_events_total{event="user.signed_up"}[1h])
Fehler sollten vollständig dokumentiert werden:
try {
await database.query('SELECT * FROM users');
} catch (error) {
logger.error('Database query failed', {
workspace_id: 'org-123',
component: 'database',
query_type: 'select',
table: 'users',
error_code: error?.code,
error_message: error?.message,
retry_count: 3,
// ❌ NIE: error_details: error.stack → Stack traces sind public!
});
}# Alle Fehler in letzten 5 Minuten
{level="error", workspace_id="org-123"} | json | line_format "{{.error_code}}: {{.error_message}}"
# Fehler pro Komponente
sum(count_over_time({level="error"}[5m])) by (component)
Unit Tests sollten auch die Observability testen:
// packages/monitoring-client/scripts/pii-redaction-test.ts
import { getSessionFromRedis, createSessionInRedis } from '@sva/sdk';
describe('PII Redaction', () => {
it('redacts email addresses in logs', async () => {
const logger = createSdkLogger({ component: 'test' });
logger.info('User login', {
email: '[email protected]', // ← Will be masked
workspace_id: 'test-ws'
});
// Verify in Loki that email was masked
const logs = await queryLoki('{workspace_id="test-ws"}');
expect(logs[0].email).toBe('j***@example.com');
});
});describe('Workspace Context', () => {
it('propagates workspace_id through async operations', async () => {
await runWithWorkspaceContext(
{ workspaceId: 'org-123' },
async () => {
const context = getWorkspaceContext();
expect(context.workspaceId).toBe('org-123');
}
);
});
});Für Entwicklung:
- Grafana: http://localhost:3001
- Prometheus: http://localhost:9090
- Loki: http://localhost:3100
- OTEL Collector: http://localhost:4318 (OTLP/HTTP) oder :4317 (gRPC)
- Prüfe Docker Container Logs:
docker logs sva-studio-promtail - Prüfe Loki Health:
curl http://localhost:3100/ready - Prüfe Promtail Config: Path zum Log File korrekt? (
__path__in static_configs)
- Prüfe OTEL Collector Logs:
docker logs sva-studio-otel-collector - Prüfe OTEL Health:
curl http://localhost:13133/healthz - Prüfe Prometheus Targets: http://localhost:9090/targets (sollte OTEL Collector upstreaming)
- Test manuell:
pnpm exec tsx packages/monitoring-client/scripts/pii-redaction-test.ts - Prüfe Promtail Config regex
- Prüfe OTEL SDK forbiddenLabels in
packages/monitoring-client/src/otel.ts