How NLog Logging Is Configured and Verbose Logging Works in Shadowsocks-Windows

Shadowsocks-Windows configures NLog through an XML file embedded as an assembly resource, mapping Debug/Trace levels to verbose mode while allowing runtime toggling without application restart.

Shadowsocks-Windows utilizes NLog as its unified logging framework to capture diagnostic information and runtime events. The application manages its logging configuration through a dynamic XML file that ships as an embedded resource and gets written to disk on first launch. Understanding how this NLog logging system is configured and how verbose logging works reveals the mechanisms behind the application's diagnostic capabilities and the seamless way users can adjust log verbosity on the fly.

Configuration File Initialization and Loading

The logging lifecycle begins when the application starts. In shadowsocks-csharp/Program.cs, the entry point immediately invokes Model.NLogConfig.TouchAndApplyNLogConfig() to ensure the configuration file exists before any logging occurs.

Embedded Resource and First-Run Setup

The default XML configuration lives as an embedded resource named NLog_config inside shadowsocks-csharp/Properties/Resources.Designer.cs. During first run, the application checks for the presence of NLog.config in the working directory. If the file is missing, the startup code extracts the embedded resource and writes it to disk as "NLog.config".

The TouchAndApplyNLogConfig Method

Located in shadowsocks-csharp/Model/NLogConfig.cs, the TouchAndApplyNLogConfig() method performs two critical operations:

  1. File Creation: Writes the embedded XML to disk if NLog.config does not exist
  2. Configuration Loading: Invokes LoadConfiguration() which calls LogManager.LoadConfiguration(NLOG_CONFIG_FILE_NAME) to apply settings to the global LogManager

This ensures NLog is fully initialized before the main application window appears.

Understanding Verbose Logging Levels

Verbose logging in Shadowsocks-Windows is not a separate logging framework but rather a mapping of NLog's native levels to a user-facing boolean flag.

Mapping NLog Levels to Verbose Mode

During configuration initialization in shadowsocks-csharp/Model/Configuration.cs, the Process() method reads the XML via NLogConfig.LoadXML() and checks the minlevel attribute through GetLogLevel(). The logic follows this strict mapping:

switch (config.nLogConfig.GetLogLevel())
{
    case NLogConfig.LogLevel.Debug:
    case NLogConfig.LogLevel.Trace:
        config.isVerboseLogging = true;   // verbose mode enabled
        break;
    default:
        config.isVerboseLogging = false;  // normal mode
}
  • Debug or Trace → isVerboseLogging = true (Verbose mode)
  • Fatal, Error, Warn, or Info → isVerboseLogging = false (Normal mode)

The Configuration Flag

The isVerboseLogging boolean is persisted in gui-config.json and drives the UI checkbox state in shadowsocks-csharp/View/MenuViewController.cs. This creates a bridge between the technical NLog levels and user-friendly application settings.

Runtime Toggle Without Restart

A key feature of the implementation is the ability to change logging verbosity without restarting the application. This is handled through the ToggleVerboseLogging pipeline.

The ToggleVerboseLogging Flow

When a user clicks the "Verbose Logging" menu item (handled in MenuViewController.cs), the following sequence executes:

  1. Flag Update: Configuration.isVerboseLogging is set to the requested value
  2. Level Calculation: verboseLogLevel is determined—Debug for release builds and Trace for DEBUG builds
  3. XML Modification: The code updates the NLog configuration object and writes it back:
config.nLogConfig.SetLogLevel(
    config.isVerboseLogging ? verboseLogLevel : NLogConfig.LogLevel.Info);
NLogConfig.SaveXML(config.nLogConfig);
  1. Configuration Reload: ShadowsocksController.ToggleVerboseLogging triggers NLogConfig.LoadConfiguration() to reload the modified XML

Reloading Configuration Dynamically

Because the code calls LogManager.LoadConfiguration() again with the updated file, all existing logger instances automatically respect the new minimum level. Loggers are typically declared as static fields throughout the codebase:

private static readonly Logger logger = LogManager.GetCurrentClassLogger();

Subsequent calls to logger.Info(), logger.Debug(), or logger.Trace() filter according to the new configuration immediately.

Log Output and Visualization

The default embedded XML defines a file target that directs all output to ss_win_temp\shadowsocks.log. The shadowsocks-csharp/View/LogForm.cs class reads this file in real-time to display logs in the user interface, providing immediate visibility into connection diagnostics and proxy events.

Implementation Examples

Reading the Current NLog Level

To programmatically check the current logging level:

var nLogConfig = NLogConfig.LoadXML();          // loads NLog.config
var level = nLogConfig.GetLogLevel();           // enum LogLevel
Console.WriteLine($"Current NLog level: {level}");

Source: shadowsocks-csharp/Model/NLogConfig.cs

Enabling Verbose Logging from Code

To force verbose mode programmatically:

var cfg = Configuration.Load();
cfg.isVerboseLogging = true;
cfg.nLogConfig.SetLogLevel(
    cfg.isVerboseLogging ? NLogConfig.LogLevel.Debug : NLogConfig.LogLevel.Info);
NLogConfig.SaveXML(cfg.nLogConfig);
NLogConfig.LoadConfiguration();

Relevant sections: Configuration.cs Save method, ShadowsocksController.cs ToggleVerboseLogging

Writing Log Statements

Any class can log using the standard NLog pattern:

private static readonly Logger logger = LogManager.GetCurrentClassLogger();

logger.Info("Application started");
logger.Debug("Detailed debug info – only visible when verbose logging is on");
logger.Trace("Trace-level tracing – shown when the config level is Trace");

Examples appear throughout Program.cs and ShadowsocksController.cs

Summary

  • Shadowsocks-Windows uses NLog with an XML configuration file (NLog.config) that ships as an embedded resource in Resources.Designer.cs
  • The TouchAndApplyNLogConfig() method in NLogConfig.cs handles first-run file creation and initial loading via LogManager.LoadConfiguration()
  • Verbose mode maps to NLog levels Debug (Release builds) or Trace (DEBUG builds), stored in the isVerboseLogging flag within Configuration.cs
  • Runtime toggling updates both gui-config.json and the NLog.config XML, then reloads the configuration without requiring an application restart
  • Logs write to ss_win_temp\shadowsocks.log and display in the LogForm UI component

Frequently Asked Questions

How does Shadowsocks-Windows create the NLog configuration file on first run?

The application calls Model.NLogConfig.TouchAndApplyNLogConfig() during startup in Program.cs. This method checks for the existence of NLog.config on disk, and if missing, extracts the embedded resource NLog_config from Resources.Designer.cs and writes it to the working directory before loading it into the NLog LogManager.

What is the difference between Debug and Trace levels in verbose logging?

In release builds, verbose logging uses the Debug level, while DEBUG builds use Trace for maximum granularity. Both levels set isVerboseLogging to true in the configuration, but Trace provides more detailed diagnostic information than Debug during development.

Can I change the logging level without restarting the application?

Yes. When you toggle verbose logging via the UI menu, MenuViewController.cs invokes ShadowsocksController.ToggleVerboseLogging(bool), which updates the configuration object, writes the new level to the XML file via NLogConfig.SaveXML(), and immediately reloads the configuration using NLogConfig.LoadConfiguration(). NLog applies these changes dynamically to all existing logger instances.

Where are the log files stored and how can I view them?

By default, logs are written to ss_win_temp\shadowsocks.log as defined in the XML target configuration. You can view these logs directly in the file system or through the built-in log viewer accessible from the application menu, which is implemented in shadowsocks-csharp/View/LogForm.cs and reads the log file in real-time.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →