Skip to content

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 search command 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 metni

Example:

[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.gz

Turkish 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 termTurkish termExample in context
ExceptionIstisnaSystem.NullReferenceException: Bos Referans Istisnasi
Inner ExceptionIc IstisnaIc Istisna: System.IO.FileNotFoundException
ThreadIs ParcacigiIs Parcacigi #42: EconomyUpdateThread
Stack TraceYigin IslemiYigin Islemi (5 frames):
at / inicindeicinde EconomyPlugin.Handlers.BalanceCheckHandler.CalistirAsync
Null ReferenceBos Referans HatasiNesne referansi bir nesne ornegine ayarlanmamis.
MethodYontemCalistirAsync yontemi
ParameterParametreparametre: ulong playerId
ArgumentBagimsiz Degiskenbagimsiz degisken: 76561198012345678
TypeTurTur: EconomyPlugin
AssemblyButunlesmeButunlesme: EconomyPlugin, Surum 1.0.0.0
Line NumberSatir Numarasisatir 42
FileDosyaDosya: BalanceCheckHandler.cs
ObjectNesneNesne referansi
ArrayDiziDizi boyutu asildi
StringMetin DizisiMetin Dizisi bekleniyordu
BooleanMantiksal DegerMantiksal Deger: true
IntegerTam SayiTam Sayi: 42
FloatOndalikli SayiOndalikli Sayi: 3.14
ConversionDonusumTur donusumu basarisiz
CastTur DonusumuGecersiz tur donusumu
IndexIndeksDizi indeksi sinirlarin disinda
KeyAnahtarAnahtar bulunamadi
ValueDegerDeger null olamaz
Out of MemoryBellek YetersizBellek yetersiz istisnasi
TimeoutZaman AsimiZaman asimi istisnasi
Invalid OperationGecersiz IslemselGecersiz islemsel istisna
Not ImplementedUygulanmadiYontem 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 156

Translated 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 156

Disabling translation per plugin

yaml
logging:
  stack_trace_translation:
    enabled: true
    exclude_plugins:
      - "custom:my-plugin"
    max_depth: 50

When 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

FieldTransmittedPseudonymized
TimestampYesNo
SeverityYesNo
Plugin IDYesNo
Message textYesPersonal data removed
Steam IDOne-way SHA-256 hashYes
IP addressFirst two octets onlyYes
Chat contentStrippedN/A
Command argumentsStrippedN/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.plugin

Output:

[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:42

Debugger command reference

CommandTurkishAliasesDescription
stepadims, nextExecute next line of code
continuedevamc, resumeResume normal execution until next breakpoint
breakkesintibSet breakpoint at current line
deletesildDelete breakpoint
listlistelList nearby source code (10 lines)
printyazdirpPrint variable value
stackyiginbt, wherePrint current stack trace
threadsis_parcaciklaritList active threads
localsyerellerlocPrint all local variables
evaluatedegerlendirevalEvaluate expression
detachayrilq, quitDetach debugger from plugin
helpyardim?, hShow 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: false

Log search command

openmod logs search \
  --player 76561198012345678 \
  --severity KRITIK \
  --since "2026-03-01" \
  --until "2026-04-01" \
  --plugin anti-cheat.core \
  --output json

Output:

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:

  1. Log rotation fails — cannot compress or archive.
  2. All logging except Kritik is suspended.
  3. Kritik messages are written to the Windows Application Event Log (or syslog on Linux).
  4. 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 rotation

The 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: true

The 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 kvkk

The 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