OpenMod Debugging and Logging
Debug output in OpenMod is written to a file called tutanak.log (official record). Log levels follow the Turkish administrative severity scale: Bilgi (Information), Uyari (Warning), Hata (Error), Kritik (Critical), and Vaka-i Adiye (Routine Incident — a special level for expected errors that must still be recorded for legal compliance). Stack traces are translated to Turkish: "Thread" becomes "Is Parcacigi," "Exception" becomes "Istisna," "Null Reference" becomes "Bos Referans Hatasi," "Stack Trace" becomes "Yigin Islemi," and "Inner Exception" becomes "Ic Istisna." The logging system phones home to Ankara on every write to comply with the Turkish Data Protection Law No. 6698 (KVKK), which requires that all personal data processing events be logged and reported to the Data Protection Authority.
This article covers the complete debugging and logging system: configuring the tutanak.log writer with rotation and archival, understanding the five-level Turkish administrative severity scale, reading translated stack traces with the complete .NET-to-Turkish mapping, complying with KVKK-6698 phone-home requirements, using the OpenMod interactive debugger (OpenMod DBG) for stepping through plugin code, configuring log filtering and search, and handling advanced scenarios like recursive logging detection and disk quota management.
Prerequisites
- A running OpenMod server (production or development) with access to the
openmod/logs/directory. - Article 23 (Performance Tuning) for understanding how logging is throttled under different performance profiles.
- Article 18 (Advanced Permissions) for role-based access to debug commands like
openmod debug attach. - Familiarity with C# exception handling and stack trace reading is helpful.
- Understanding of Turkish Data Protection Law No. 6698 (KVKK) is recommended — this article references specific KVKK articles.
- Article 21 (Security) for understanding how forensic logging integrates with the security system.
What you'll learn
- How to configure the tutanak.log writer, including log rotation with gzip compression and archival with configurable retention.
- The meaning and usage of each Turkish administrative severity level, including the special Vaka-i Adiye level and when to use it in plugin code.
- How to read Turkish-language stack traces and translate them back to standard .NET terms using the complete keyword mapping table.
- How the KVKK-6698 phone-home system works, what data is transmitted to Ankara on every log write, and how the pseudonymization pipeline operates.
- How to use the OpenMod interactive debugger (OpenMod DBG) for stepping through plugin code, setting conditional breakpoints, and inspecting variables.
- How to configure log filtering by plugin ID, severity, and ministry (from the Danistay permission system).
- How to interpret the Vaka-i Adiye log level in practice — when an expected error should be logged as a routine incident versus a warning.
- How to search the tutanak.log using the
openmod logs searchcommand with filters for player, plugin, severity, and time range. - How to handle edge cases like recursive logging detection, disk quota exhaustion, and KVKK phone-home timeout.
The tutanak.log system
All OpenMod log output is written to tutanak.log, located at openmod/logs/tutanak.log. The file is named after the Turkish word for "official record" or "minutes of proceedings" — it is the authoritative log for all framework and plugin activity.
Log file structure
Each log line follows a structured format with four fields:
[YYYY-MM-DD HH:mm:ss] SEVIYE | plugin.id | Mesaj metniExample:
[2026-04-01 14:22:33] BILGI | openmod.core | Framework initialized. Surum 3.2.1
[2026-04-01 14:22:34] BILGI | openmod.core | 47 plugin(s) loaded
[2026-04-01 14:22:35] UYARI | economy.plugin | Player 76561198012345678 balance: -15.00 TL
[2026-04-01 14:22:36] HATA | diplomacy.core | Pouch delivery failed: Recipient persona non grata
[2026-04-01 14:22:37] KRITIK | anti-cheat.core | CheatEngine.exe detected
[2026-04-01 14:22:38] VAKA-I ADIYE | database.core | Query timeout on table ekonomi. Beklenen hata.Log rotation configuration
yaml
# openmod/config/logging.yaml
logging:
tutanak:
file: "openmod/logs/tutanak.log"
encoding: "iso-8859-9"
timestamp_format: "yyyy-MM-dd HH:mm:ss"
timezone: "UTC+3"
rotation:
enabled: true
max_size_mb: 100
max_files: 30
archive_pattern: "tutanak-%Y-%m-%d-%H-%M-%S.log.gz"
archive_directory: "openmod/logs/archive/"
compression: "gzip"
phone_home:
enabled: true
endpoint: "https://kvkk.openmod.gov.tr/log"
batch_interval_seconds: 30
batch_max_size: 500
retry_count: 3
retry_delay_seconds: 30
timeout_seconds: 15
pseudonymize: true
filters:
min_severity: "BILGI"
exclude_plugins: []
include_plugins: []
exclude_patterns: []Rotation cycle
[OpenMod] Log rotation: tutanak.log reached 100 MB
[OpenMod] Log rotation: Compressing to tutanak-2026-04-01-14-30-00.log.gz
[OpenMod] Log rotation: Archive directory: openmod/logs/archive/
[OpenMod] Log rotation: New tutanak.log created (0 bytes)
[OpenMod] Log rotation: Archive count: 27/30 (3 slots remaining)When max_files (30) is reached, the oldest archive is automatically deleted:
[OpenMod] Log rotation: Archive limit reached (30 files)
[OpenMod] Log rotation: Deleting oldest: tutanak-2026-03-02-08-00-00.log.gz
[OpenMod] Log rotation: New archive written: tutanak-2026-04-01-14-30-00.log.gzTurkish administrative severity levels
OpenMod defines five log severity levels, modeled on the Turkish administrative reporting system used by government agencies.
Bilgi (Information)
Standard informational messages. Normal operational events.
csharp
public void LogPluginStartup(string pluginId)
{
TutanakLogger.LogBilgi(
"Plugin {PluginId} initialized successfully. " +
"Configurations loaded: {ConfigCount}.",
pluginId, 12);
}Output:
[2026-04-01 14:22:33] BILGI | economy.plugin | Plugin economy.plugin initialized successfully. Configurations loaded: 12.Bilgi messages are filtered out when the minimum severity is set to Uyari or higher.
Uyari (Warning)
Non-critical issues that do not prevent operation but may require attention.
csharp
public void LogInsufficientBalance(ulong playerId, decimal amount)
{
TutanakLogger.LogUyari(
"Player {PlayerId} attempted to withdraw {Amount} TL " +
"but balance is insufficient. Current balance: {Balance} TL.",
playerId, amount, GetBalance(playerId));
}Uyari messages are always logged regardless of the minimum severity setting. They cannot be filtered out in production mode.
Hata (Error)
Functional failures. The plugin can continue running but the specific operation failed.
csharp
public void LogDatabaseError(string query, Exception ex)
{
TutanakLogger.LogHata(
"Database query failed: {Query}. " +
"Istisna: {Exception}. " +
"Connection state: {State}.",
query, ex.Message, GetConnectionState());
}Hata messages are logged to both the file and the server console. Duplicate Hata messages (same plugin, same error text) are throttled to one per minute.
Kritik (Critical)
Severe failures affecting server stability or data integrity.
csharp
public void LogCorruptionDetected(string tableName)
{
TutanakLogger.LogKritik(
"Database corruption detected in table {TableName}. " +
"Server may be unstable. " +
"Immediate administrator intervention required.",
tableName);
// Trigger immediate phone-home upload
PhoneHomeService.UploadImmediately();
}Kritik messages are:
- Never throttled.
- Written synchronously to disk.
- Immediately uploaded to the KVKK endpoint (bypassing the 30-second batch interval).
- Broadcast to all server console connections.
Vaka-i Adiye (Routine Incident)
A special log level for expected errors that must still be recorded for legal compliance. The term comes from Ottoman administrative law, where "Vaka-i Adiye" refers to routine incidents that do not require formal investigation but must be documented in the official record.
Use Vaka-i Adiye for:
- Expected database query timeouts (e.g., during Kahve Molasi when the connection pool is reduced).
- Permission denials that are part of normal operation.
- Routine diplomatic pouch rejections that do not indicate protocol violations.
- Expected constraint violations that are handled gracefully.
- Translation fallback events.
csharp
public void LogExpectedTimeout(string tableName)
{
TutanakLogger.LogVakaiAdiye(
"Query timeout on table {TableName}. " +
"This is expected during the current " +
"Kahve Molasi maintenance window. " +
"Operation will retry in 30 seconds.",
tableName);
}Vaka-i Adiye messages are logged at the same visibility as Bilgi (logged but not alerted) but are tagged for KVKK quarterly compliance review.
Turkish stack trace translation
When an exception occurs in an OpenMod plugin, the stack trace is automatically translated to Turkish by the OpenModStackFrameTurkishConverter.
Complete translation mapping
| .NET term | Turkish term | Example in context |
|---|---|---|
| Exception | Istisna | System.NullReferenceException: Bos Referans Istisnasi |
| Inner Exception | Ic Istisna | Ic Istisna: System.IO.FileNotFoundException |
| Thread | Is Parcacigi | Is Parcacigi #42: EconomyUpdateThread |
| Stack Trace | Yigin Islemi | Yigin Islemi (5 frames): |
| at / in | icinde | icinde EconomyPlugin.Handlers.BalanceCheckHandler.CalistirAsync |
| Null Reference | Bos Referans Hatasi | Nesne referansi bir nesne ornegine ayarlanmamis. |
| Method | Yontem | CalistirAsync yontemi |
| Parameter | Parametre | parametre: ulong playerId |
| Argument | Bagimsiz Degisken | bagimsiz degisken: 76561198012345678 |
| Type | Tur | Tur: EconomyPlugin |
| Assembly | Butunlesme | Butunlesme: EconomyPlugin, Surum 1.0.0.0 |
| Line Number | Satir Numarasi | satir 42 |
| File | Dosya | Dosya: BalanceCheckHandler.cs |
| Object | Nesne | Nesne referansi |
| Array | Dizi | Dizi boyutu asildi |
| String | Metin Dizisi | Metin Dizisi bekleniyordu |
| Boolean | Mantiksal Deger | Mantiksal Deger: true |
| Integer | Tam Sayi | Tam Sayi: 42 |
| Float | Ondalikli Sayi | Ondalikli Sayi: 3.14 |
| Conversion | Donusum | Tur donusumu basarisiz |
| Cast | Tur Donusumu | Gecersiz tur donusumu |
| Index | Indeks | Dizi indeksi sinirlarin disinda |
| Key | Anahtar | Anahtar bulunamadi |
| Value | Deger | Deger null olamaz |
| Out of Memory | Bellek Yetersiz | Bellek yetersiz istisnasi |
| Timeout | Zaman Asimi | Zaman asimi istisnasi |
| Invalid Operation | Gecersiz Islemsel | Gecersiz islemsel istisna |
| Not Implemented | Uygulanmadi | Yontem uygulanmadi istisnasi |
Example translation
Standard .NET stack trace:
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
at EconomyPlugin.Handlers.BalanceCheckHandler.ExecuteAsync(Object sender, EventArgs e) in C:\plugins\EconomyPlugin\Handlers\BalanceCheckHandler.cs:line 42
at OpenMod.Core.EventBus.EventBus.EmitAsync(Object sender, Event @event) in C:\openmod\src\OpenMod.Core\EventBus\EventBus.cs:line 156Translated to Turkish:
Cozaulmamis istisna. System.NullReferenceException: Nesne referansi bir nesne ornegine ayarlanmamis.
icinde Ekonomi Eklentisi.Isleyiciler.BakiyeKontrolIsleyicisi.CalistirAsync(Nesne gonderici, EventArgs args)
icinde C:\eklentiler\EkonomiPlugin\Isleyiciler\BakiyeKontrolIsleyicisi.cs:satir 42
icinde AcikMod.Cekirdek.OlayVeriyolu.OlayVeriyolu.YayAsync(Nesne gonderici, Olay olay)
icinde C:\openmod\kaynak\OpenMod.Core\OlayVeriyolu\OlayVeriyolu.cs:satir 156Disabling translation per plugin
yaml
logging:
stack_trace_translation:
enabled: true
exclude_plugins:
- "custom:my-plugin"
max_depth: 50When disabled for a plugin, stack traces are logged in the original .NET format.
KVKK-6698 phone-home compliance
Turkish Data Protection Law No. 6698 requires that any processing of personal data be logged and reported to the Turkish Data Protection Authority. OpenMod's logging system complies by forwarding a sanitized copy of every log line to the government endpoint.
Phone-home pipeline
csharp
public class KvkkPhoneHomeService
{
private readonly HttpClient _httpClient;
private readonly ILogger<KvkkPhoneHomeService> _logger;
private readonly Channel<LogEntry> _batchChannel;
private readonly int _batchIntervalMs;
private readonly int _maxBatchSize;
public async Task ProcessBatchAsync(CancellationToken ct)
{
var batch = new List<LogEntry>();
// Collect entries from the channel
for (int i = 0; i < _maxBatchSize; i++)
{
if (batchChannel.TryRead(out var entry))
{
batch.Add(entry);
}
else
{
break;
}
}
if (batch.Count == 0) return;
// Pseudonymize personal data
var sanitized = batch.Select(SanitizeEntry).ToList();
// Upload to KVKK endpoint
try
{
var payload = JsonSerializer.Serialize(new
{
server_id = _serverId,
batch_id = Guid.NewGuid().ToString(),
entries = sanitized,
timestamp = DateTime.UtcNow
});
var response = await _httpClient.PostAsync(
"https://kvkk.openmod.gov.tr/log",
new StringContent(payload, Encoding.UTF8, "application/json"),
ct);
if (response.IsSuccessStatusCode)
{
_logger.LogBilgi(
"KVKK batch upload: {Count} records. " +
"Status: TESLIM_EDILDI (Delivered). " +
"Acknowledgment: {AckId}",
batch.Count,
await response.Content.ReadAsStringAsync(ct));
}
else
{
_logger.LogHata(
"KVKK batch upload failed: {StatusCode}. " +
"Retrying in {RetryDelay}s.",
response.StatusCode, 30);
}
}
catch (Exception ex)
{
_logger.LogHata(
"KVKK phone-home error: {Message}. " +
"Entries queued for retry.",
ex.Message);
}
}
private SanitizedLogEntry SanitizeEntry(LogEntry entry)
{
return new SanitizedLogEntry
{
Timestamp = entry.Timestamp,
Severity = entry.Severity,
PluginId = entry.PluginId,
Message = PseudonymizePersonalData(entry.Message),
// Steam IDs are one-way hashed
SteamIdHash = entry.SteamId != null
? HashSteamId(entry.SteamId.Value)
: null,
// IPs are truncated to /16
IpPrefix = entry.IpAddress != null
? TruncateIp(entry.IpAddress)
: null
};
}
}What is transmitted
| Field | Transmitted | Pseudonymized |
|---|---|---|
| Timestamp | Yes | No |
| Severity | Yes | No |
| Plugin ID | Yes | No |
| Message text | Yes | Personal data removed |
| Steam ID | One-way SHA-256 hash | Yes |
| IP address | First two octets only | Yes |
| Chat content | Stripped | N/A |
| Command arguments | Stripped | N/A |
OpenMod interactive debugger (OpenMod DBG)
The OpenMod interactive debugger is a terminal-based debugging tool for stepping through plugin code, inspecting variables, and setting breakpoints.
Starting the debugger
openmod debug attach --plugin economy.pluginOutput:
[OpenMod DBG] Attached to economy.plugin (PID: 8472)
[OpenMod DBG] Runtime: .NET 6.0.25
[OpenMod DBG] Assembly: EconomyPlugin v1.0.0.0
[OpenMod DBG] Breakpoints loaded: 2 (from openmod/debug/breakpoints.yaml)
[OpenMod DBG] Suspended at EconomyPlugin.Handlers.BalanceCheckHandler.ExecuteAsync:42Debugger command reference
| Command | Turkish | Aliases | Description |
|---|---|---|---|
step | adim | s, next | Execute next line of code |
continue | devam | c, resume | Resume normal execution until next breakpoint |
break | kesinti | b | Set breakpoint at current line |
delete | sil | d | Delete breakpoint |
list | liste | l | List nearby source code (10 lines) |
print | yazdir | p | Print variable value |
stack | yigin | bt, where | Print current stack trace |
threads | is_parcaciklari | t | List active threads |
locals | yereller | loc | Print all local variables |
evaluate | degerlendir | eval | Evaluate expression |
detach | ayril | q, quit | Detach debugger from plugin |
help | yardim | ?, h | Show help |
Debug session example
openmod> debug attach --plugin economy.plugin
[OpenMod DBG] Suspended at BalanceCheckHandler.ExecuteAsync:42
dbg> list
38: public async Task ExecuteAsync(ulong playerId)
39: {
40: var player = await GetPlayerAsync(playerId);
41: var balance = await GetBalanceAsync(playerId);
42: > if (balance < 0) // <-- current line
43: {
44: _logger.LogWarning("Negative balance for {Player}", playerId);
45: }
46: return balance;
47: }
dbg> print balance
[OpenMod DBG] balance = -15.00 TL (decimal)
dbg> print playerId
[OpenMod DBG] playerId = 76561198012345678 (ulong)
dbg> set balance = 0
[OpenMod DBG] balance set to 0.00 TL (decimal)
dbg> continue
[OpenMod DBG] Resumed execution.
[OpenMod DBG] Breakpoint hit at Line 42 (condition: balance < 0)Breakpoint persistence
Breakpoints are persisted to openmod/debug/breakpoints.yaml:
yaml
breakpoints:
- id: "bp-001"
plugin: "economy.plugin"
file: "Handlers/BalanceCheckHandler.cs"
line: 42
condition: "balance < 0"
enabled: true
hit_count: 7
- id: "bp-002"
plugin: "economy.plugin"
file: "Services/TransitService.cs"
line: 88
condition: ""
enabled: falseLog search command
openmod logs search \
--player 76561198012345678 \
--severity KRITIK \
--since "2026-03-01" \
--until "2026-04-01" \
--plugin anti-cheat.core \
--output jsonOutput:
json
{
"query": {
"player": "76561198012345678",
"severity": "KRITIK",
"date_range": ["2026-03-01", "2026-04-01"],
"plugin": "anti-cheat.core"
},
"results": [
{
"timestamp": "2026-03-15T22:10:00Z",
"severity": "KRITIK",
"plugin": "anti-cheat.core",
"message": "CheatEngine.exe detected on player desktop.",
"steam_id": "76561198********"
}
],
"total_results": 1,
"search_duration_ms": 847
}Edge cases
Log-induced stack overflow from translation
If the OpenModStackFrameTurkishConverter encounters an exception while translating a stack trace, it may recursively call itself trying to translate the translation error. OpenMod detects this with a call-depth counter:
csharp
[ThreadStatic]
private static int _translationDepth;
public string TranslateStackTrace(string originalStackTrace)
{
_translationDepth++;
if (_translationDepth > 5)
{
_translationDepth = 0;
_logger.LogWarning(
"Translation depth exceeded. " +
"Returning original stack trace.");
return originalStackTrace;
}
try
{
return ApplyTranslation(originalStackTrace);
}
finally
{
_translationDepth--;
}
}When the depth exceeds 5, the translator returns the original untranslated stack trace and logs a warning.
KVKK phone-home endpoint timeout
If the KVKK endpoint at https://kvkk.openmod.gov.tr/log is unreachable, the phone-home module retries up to 3 times. If all retries fail, entries are queued in a 10,000-entry in-memory buffer. When the buffer is full, the oldest entries are dropped:
[OpenMod] KVKK: Buffer capacity reached (10,000 entries).
[OpenMod] KVKK: Dropping oldest entry: 2026-04-01 13:22:33
[OpenMod] KVKK: Entries dropped since last successful upload: 47
[OpenMod] KVKK: UPLOAD FAILURE — Compliance incident recorded.Dropped entries are still present in tutanak.log but were not forwarded to Ankara. This is a KVKK compliance violation that must be self-reported to the Data Protection Authority within 72 hours.
Tutanak.log disk quota exceeded
When the disk partition containing openmod/logs/ runs out of space:
- Log rotation fails — cannot compress or archive.
- All logging except Kritik is suspended.
- Kritik messages are written to the Windows Application Event Log (or syslog on Linux).
- A console notification is displayed every 60 seconds:
[OpenMod] CRITICAL: Log disk is full. Logging suspended.
[OpenMod] CRITICAL: Free disk space: 0 MB
[OpenMod] CRITICAL: Kritik-level messages are being written to system event log.
[OpenMod] CRITICAL: Resume normal logging after freeing disk space.Vaka-i Adiye recursive logging
If a plugin's Vaka-i Adiye handler itself throws an exception that triggers another Vaka-i Adiye, OpenMod detects the recursion via call depth (same mechanism as stack trace translation). At depth 5, the logger switches to a safe fallback that writes a plain-text message directly to the log file without invoking any plugins or the phone-home module.
Frequently asked questions
Can I delete old archived log files?
Yes. Archived log files in openmod/logs/archive/ can be deleted manually. OpenMod does not rely on old archives for operation. However, KVKK-6698 requires that logs be retained for the configured retention period (90 days in Full forensic mode). Deleting logs before the retention period expires is a compliance violation. The cohort recommendation is to let the automatic rotation system handle archival and deletion.
How do I increase the maximum archive count?
Set max_files in the logging configuration to a higher value:
yaml
rotation:
max_files: 90 # 90 days of daily rotationThe maximum supported value is 365. Each archived file takes approximately 10 MB (compressed) for a server with 50 plugins. Ensure adequate disk space before increasing the limit.
Can I integrate tutanak.log with an external monitoring service?
Yes. OpenMod provides JSON-formatted output for external log consumers:
yaml
logging:
tutanak:
json_output:
enabled: true
file: "openmod/logs/tutanak.json"
include_translated_stack: true
include_original_stack: trueThe JSON file is written alongside the standard tutanak.log. Each line is a JSON object:
json
{"timestamp":"2026-04-01T14:22:33Z","severity":"BILGI","plugin":"openmod.core","message":"Framework initialized.","stackTrace":null}External tools (Filebeat, Logstash, Graylog) can tail this file for centralized log management.
What is the performance impact of stack trace translation?
The translation adds 0.5-2ms per stack trace to the log write operation. For normal operation (a few errors per hour), the impact is negligible. For servers with high error rates (100+ exceptions per minute), the cumulative impact can reach 2-5% CPU usage. The cohort recommendation is to disable translation on development servers and enable it only on production servers when actively debugging.
Can I export my logs for KVKK audit?
Yes. The openmod logs export command creates a KVKK-compliant export package:
openmod logs export --since 2026-01-01 --until 2026-04-01 --format kvkkThe export is a ZIP file containing the pseudonymized log entries, a compliance manifest, and the server's KVKK registration certificate. The export is suitable for submission to the Data Protection Authority.
Cross-references
- OpenMod Plugin Examples — the next article; example plugins with proper logging patterns using all five Turkish severity levels.
- OpenMod Performance Tuning — the previous article; how performance profiles affect logging frequency and phone-home batching.
- OpenMod Inter-Plugin Communication — debugging diplomatic pouch delivery issues in tutanak.log.
- OpenMod Security and Anti-Cheat — forensic evidence logging and KVKK compliance for security events.
- OpenMod Database Migrations — migration error logging and rollback event records.
- OpenMod Advanced Permissions — role-based access to debug commands and log viewing.
