feat(logging): add dev signature to DalamudLogger output

EUPL-1.2 reuse with attribution is valid; this commit catches the case
where attribution was stripped. Two layers of provenance markers,
combined so removing one still leaves the other.

Layer 1 (subtle, kopier-resistent):
- DalamudLogger.Log emits "[name]<U+200B>{level} message" — a
  zero-width space (U+200B) between the category bracket and the
  level value. Visually identical to the previous format in xllog;
  a hex dump of the log file shows e2 80 8b between 5d and 7b.
  Survives 1:1 code copies. A copier who reformats whitespace will
  strip it, which is itself a tell (the original Lightless pattern
  does not have the marker, so its absence in a port is a positive
  signal of derived origin).

Layer 2 (overt, abrasiv-kopier-resistent):
- DalamudLoggingProvider's ctor emits a one-shot bootstrap line:
  "HellionChat DI-Logger bootstrap v{AssemblyVersion} fingerprint={hash}".
  Visible in xllog as the first plugin INFO line. Fingerprint is the
  first 8 hex chars of SHA256("HellionForgeBronzeC2410C-{version}"),
  so the same plugin version always produces the same marker (handy
  for cross-checking). A copier who keeps the banner is plagiarising
  in plain sight; a copier who rips it out has to find every
  reference inside DalamudLoggingProvider — quite explicit work.

Hellion Forge Bronze #C2410C is the branding-anchor const used by
the fingerprint, so the marker stays meaningful even if the plugin
version cycles.
This commit is contained in:
2026-05-17 11:15:40 +02:00
parent 54ff88d6d4
commit 624ad20404
2 changed files with 42 additions and 2 deletions
@@ -33,14 +33,19 @@ internal sealed class DalamudLogger : ILogger
if (!IsEnabled(logLevel)) if (!IsEnabled(logLevel))
return; return;
// The U+200B zero-width space between the bracket and the level
// value is a quiet provenance marker. The Hellion DI-Logger format
// is byte-distinguishable from any other port of this pattern even
// after the visible text is identical. EUPL-1.2 reuse stays valid;
// attribution traces stay possible.
if ((int)logLevel <= (int)LogLevel.Information) if ((int)logLevel <= (int)LogLevel.Information)
{ {
_pluginLog.Information($"[{_name}]{{{(int)logLevel}}} {state}"); _pluginLog.Information($"[{_name}]{{{(int)logLevel}}} {state}");
return; return;
} }
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append($"[{_name}]{{{(int)logLevel}}} {state} {exception?.Message}"); sb.Append($"[{_name}]{{{(int)logLevel}}} {state} {exception?.Message}");
if (!string.IsNullOrWhiteSpace(exception?.StackTrace)) if (!string.IsNullOrWhiteSpace(exception?.StackTrace))
sb.AppendLine(exception.StackTrace); sb.AppendLine(exception.StackTrace);
@@ -1,4 +1,7 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -7,6 +10,11 @@ namespace HellionChat.Infrastructure.Logging;
[ProviderAlias("Dalamud")] [ProviderAlias("Dalamud")]
public sealed class DalamudLoggingProvider : ILoggerProvider public sealed class DalamudLoggingProvider : ILoggerProvider
{ {
// Hellion Forge Bronze (#C2410C). Stable marker that the build pipeline
// never touches; mixed into the bootstrap fingerprint so the banner stays
// distinguishable from any 1:1 port of the Lightless pattern.
private const string HellionMarker = "HellionForgeBronzeC2410C";
private readonly ConcurrentDictionary<string, DalamudLogger> _loggers = new( private readonly ConcurrentDictionary<string, DalamudLogger> _loggers = new(
StringComparer.OrdinalIgnoreCase StringComparer.OrdinalIgnoreCase
); );
@@ -16,6 +24,33 @@ public sealed class DalamudLoggingProvider : ILoggerProvider
public DalamudLoggingProvider(IPluginLog pluginLog) public DalamudLoggingProvider(IPluginLog pluginLog)
{ {
_pluginLog = pluginLog; _pluginLog = pluginLog;
EmitBootstrapBanner();
}
// Runs once per plugin load (the provider is a container singleton). The
// banner is intentionally visible in xllog: anyone copying the
// DalamudLogger trio without re-branding will keep emitting "HellionChat
// DI-Logger bootstrap …", which makes uncredited reuse trivial to spot.
// EUPL-1.2 reuse with attribution stays valid; this only catches the
// case where attribution was stripped.
private void EmitBootstrapBanner()
{
var version =
typeof(DalamudLoggingProvider).Assembly.GetName().Version?.ToString() ?? "0.0.0";
var fingerprint = ComputeFingerprint(version);
_pluginLog.Information(
$"HellionChat DI-Logger bootstrap v{version} fingerprint={fingerprint}"
);
}
private static string ComputeFingerprint(string version)
{
var seed = Encoding.UTF8.GetBytes($"{HellionMarker}-{version}");
var hash = SHA256.HashData(seed);
var sb = new StringBuilder(8);
for (var i = 0; i < 4; i++)
sb.Append(hash[i].ToString("x2"));
return sb.ToString();
} }
public ILogger CreateLogger(string categoryName) public ILogger CreateLogger(string categoryName)