📝 Logging Standards¶
🎯 Log Levels¶
╔═══════════════════════════════════════════════════════════╗ ║ Level │ Cuándo usar │ Ejemplo ║ ╠═════════╪════════════════════════════╪═══════════════════╣ ║ DEBUG │ Dev diagnostics │ "Buffer size=512" ║ ║ INFO │ Important events │ "Plugin loaded" ║ ║ WARNING │ Recoverable issues │ "High CPU 85%" ║ ║ ERROR │ Failures, exceptions │ "File not found" ║ ║ FATAL │ Unrecoverable crash │ "Out of memory" ║ ╚═══════════════════════════════════════════════════════════╝
Level Guidelines¶
DEBUG - Variable values during development - Internal state inspection - Algorithm step-by-step execution - Only in development builds
INFO - Application lifecycle events (startup, shutdown) - Plugin/component loading - Configuration changes - Sample rate changes - Audio device changes
WARNING - Performance degradation (CPU > 80%, buffer underruns) - Deprecated API usage - Recoverable errors (retry succeeded) - Configuration fallbacks - Resource limits approaching
ERROR - File I/O failures - Audio device errors - Plugin load failures - Invalid user input - Network timeouts
FATAL - Out of memory - Unhandled exceptions - Critical resource failure - Corrupted state requiring shutdown
🔒 PII Handling¶
NEVER log:¶
// ❌ BAD - Logs PII
LOG_INFO("User john.doe@example.com loaded preset");
LOG_INFO("License key: ABC-123-DEF-456");
LOG_INFO("File: C:\\Users\\JohnDoe\\Documents\\project.als");
LOG_INFO("Connected from IP: 192.168.1.100");
// ✅ GOOD - No PII
LOG_INFO("User loaded preset", {{"user_id_hash", hashUserId(user)}});
LOG_INFO("License validated");
LOG_INFO("File: project.als");
LOG_INFO("Connected from local network");
Anonymization Rules¶
| PII Type | How to Anonymize | Example |
|---|---|---|
| User ID | SHA256 hash | a3f5b8c... |
| Domain only | @example.com |
|
| File path | Relative path or filename only | project.als |
| IP address | First 3 octets + mask | 192.168.1.xxx |
| License key | First 4 chars only | ABC-xxx-xxx-xxx |
| MAC address | Hash or omit | sha256(mac) |
Implementation Example¶
// PII sanitization helper
std::string sanitizeFilePath(const std::string& fullPath) {
// Return only filename, not full path
size_t lastSlash = fullPath.find_last_of("/\\");
return (lastSlash != std::string::npos)
? fullPath.substr(lastSlash + 1)
: fullPath;
}
std::string hashUserId(const std::string& userId) {
// Simple hash for demonstration
return std::to_string(std::hash<std::string>{}(userId));
}
// Usage
LOG_INFO("Project loaded", {
{"file", sanitizeFilePath(projectPath)},
{"user_hash", hashUserId(currentUser)}
});
⚡ Performance¶
┌────────────────────────────────────────┐ │ Hot path (audio thread): │ │ → NO LOGGING (zero allocation) │ │ │ │ Cold path (UI, init): │ │ → Log libremente │ └────────────────────────────────────────┘
Audio Thread Rules¶
// ❌ NEVER in audio callback
void processAudio(float* buffer, int frames) {
LOG_DEBUG("Processing " << frames << " frames"); // ❌ ALLOCATION!
for (int i = 0; i < frames; ++i) {
buffer[i] *= gain_;
}
}
// ✅ Conditional logging outside hot path
void processAudio(float* buffer, int frames) {
// No logging in hot path
for (int i = 0; i < frames; ++i) {
buffer[i] *= gain_;
}
}
void setGain(float gain) {
gain_ = gain;
LOG_INFO("Gain changed", {{"value", gain}}); // ✅ Cold path OK
}
Performance Budget¶
| Context | Max Log Rate | Max Message Size |
|---|---|---|
| Audio thread | 0 Hz | 0 bytes |
| UI thread | 100 Hz | 1 KB |
| Background thread | 1000 Hz | 10 KB |
| Startup/shutdown | Unlimited | 100 KB |
Conditional Logging¶
// Only log in debug builds
#ifdef DEBUG
LOG_DEBUG("Internal state", {{"foo", foo}, {"bar", bar}});
#endif
// Rate limiting for repeated events
static RateLimiter limiter(std::chrono::seconds(1));
if (limiter.allow()) {
LOG_WARNING("Buffer underrun detected");
}
📐 Structured Logging¶
JSON Format¶
{
"timestamp": "2025-01-15T10:30:00.123Z",
"level": "INFO",
"logger": "AudioProcessor",
"message": "Buffer processed",
"context": {
"buffer_size": 512,
"sample_rate": 48000,
"thread_id": 12345,
"cpu_usage": 0.42
},
"tags": ["audio", "realtime"]
}
Context Fields¶
Always include:
- timestamp - ISO 8601 format with milliseconds
- level - Log level (DEBUG, INFO, WARNING, ERROR, FATAL)
- logger - Component/class name
- message - Human-readable description
Optionally include:
- thread_id - For debugging race conditions
- cpu_usage - For performance correlation
- memory_usage - For leak detection
- request_id - For distributed tracing
- session_id - For user session tracking
Implementation Example (C++)¶
// Using nlohmann/json library
#include <nlohmann/json.hpp>
void logStructured(LogLevel level, const std::string& message,
const std::map<std::string, std::string>& context) {
nlohmann::json log;
log["timestamp"] = getCurrentTimestampISO8601();
log["level"] = logLevelToString(level);
log["logger"] = getLoggerName();
log["message"] = message;
log["context"] = context;
std::cout << log.dump() << std::endl;
}
// Usage
logStructured(LogLevel::INFO, "Plugin loaded", {
{"plugin_name", "Compressor"},
{"plugin_version", "1.2.3"},
{"load_time_ms", std::to_string(loadTimeMs)}
});
Implementation Example (.NET)¶
// Using Serilog
Log.Information("Plugin loaded {@Context}", new {
PluginName = "Compressor",
PluginVersion = "1.2.3",
LoadTimeMs = loadTimeMs
});
📊 Log Message Design¶
Good Message Structure¶
[VERB] [NOUN] [OUTCOME] + Context
Examples:
✅ "Plugin loaded successfully" + {name, version, time}
✅ "Audio device started" + {device_name, sample_rate, buffer_size}
✅ "File parsing failed" + {file, error, line_number}
Bad Message Structure¶
❌ "Error" (no context)
❌ "Something went wrong" (vague)
❌ "Debug message 123" (meaningless)
❌ "TODO: add better logging" (unprofessional)
Actionable Messages¶
// ❌ Not actionable
LOG_ERROR("Processing failed");
// ✅ Actionable
LOG_ERROR("Audio buffer overflow", {
{"buffer_size", bufferSize},
{"required_size", requiredSize},
{"action", "Increase buffer size in settings"}
});
🔍 Correlation¶
Request ID Pattern¶
// Generate unique ID per operation
std::string requestId = generateUUID();
LOG_INFO("Operation started", {{"request_id", requestId}});
// ... do work ...
LOG_INFO("Operation completed", {
{"request_id", requestId},
{"duration_ms", durationMs}
});
Thread Context¶
// Thread-local storage for context
thread_local std::map<std::string, std::string> logContext;
void setLogContext(const std::string& key, const std::string& value) {
logContext[key] = value;
}
void log(LogLevel level, const std::string& message) {
// Automatically include thread context
auto fullContext = logContext;
fullContext["message"] = message;
logStructured(level, message, fullContext);
}
// Usage
setLogContext("session_id", sessionId);
setLogContext("user_hash", userHash);
// All subsequent logs in this thread include session/user context
LOG_INFO("Action performed");
📏 Size Limits¶
| Element | Limit | Reason |
|---|---|---|
| Message | 1 KB | Prevent log explosion |
| Context | 10 KB | Reasonable structured data |
| Single log | 100 KB | Max for debugging stack trace |
| Log file | 100 MB | Rolling file size |
🎭 Environment-Specific Configuration¶
// Development
LOG_LEVEL = DEBUG
LOG_OUTPUT = Console + File
// Staging
LOG_LEVEL = INFO
LOG_OUTPUT = File + ELK
// Production
LOG_LEVEL = WARNING
LOG_OUTPUT = ELK + S3 (errors only)
📚 Examples by Component¶
Audio Engine¶
LOG_INFO("Audio engine started", {
{"sample_rate", sampleRate},
{"buffer_size", bufferSize},
{"channels", numChannels}
});
LOG_WARNING("CPU usage high", {
{"cpu_percent", cpuUsage},
{"threshold", 80},
{"action", "Consider increasing buffer size"}
});
LOG_ERROR("Audio device error", {
{"device", deviceName},
{"error", errorMessage},
{"recovery", "Attempting to reconnect"}
});
Plugin Host¶
LOG_INFO("Plugin loaded", {
{"plugin", pluginName},
{"format", "VST3"},
{"latency_samples", latency}
});
LOG_WARNING("Plugin validation failed", {
{"plugin", pluginPath},
{"reason", validationError},
{"fallback", "Using default plugin"}
});