diff --git a/LoggerPro.Builder.pas b/LoggerPro.Builder.pas
index c12e5bb..1368ed6 100644
--- a/LoggerPro.Builder.pas
+++ b/LoggerPro.Builder.pas
@@ -45,6 +45,7 @@ interface
IAppenderConfigurator = interface
['{A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}']
function Done: ILoggerProBuilder;
+ function WithMasking: IMaskingAppenderConfigurator;
end;
{ Console appender configurator }
@@ -211,6 +212,12 @@ interface
function WithFilter(aFilter: TLogItemFilterFunc): IFilteredAppenderConfigurator;
end;
+ { Masking appender configurator - wraps another appender with masking functionality }
+ IMaskingAppenderConfigurator = interface(IAppenderConfigurator)
+ ['{E9F0A1B2-C3D4-2F3A-6C7D-8E9F0A1B2C3D}']
+ function WithLogLevel(aLogLevel: TLogType): IMaskingAppenderConfigurator;
+ end;
+
{ Main builder interface }
ILoggerProBuilder = interface
['{1A2B3C4D-5E6F-7A8B-9C0D-1E2F3A4B5C6D}']
@@ -270,7 +277,8 @@ implementation
LoggerPro.MemoryAppender,
LoggerPro.OutputDebugStringAppender,
LoggerPro.UDPSyslogAppender,
- LoggerPro.DBAppender.FireDAC
+ LoggerPro.DBAppender.FireDAC,
+ LoggerPro.MaskingAppender;
{$IF Defined(MSWINDOWS)}
, LoggerPro.VCLMemoAppender
, LoggerPro.VCLListBoxAppender
@@ -295,6 +303,7 @@ TBaseAppenderConfigurator = class(TInterfacedObject)
FRenderer: ILogItemRenderer;
procedure ApplyLogLevel(aAppender: ILogAppender);
function GetRenderer: ILogItemRenderer;
+ function CreateAppender: ILogAppender; virtual; abstract;
public
constructor Create(aBuilder: TLoggerProBuilder);
end;
@@ -305,6 +314,7 @@ TConsoleAppenderConfigurator = class(TBaseAppenderConfigurator, IConsoleAppend
function WithLogLevel(aLogLevel: TLogType): IConsoleAppenderConfigurator;
function WithRenderer(aRenderer: ILogItemRenderer): IConsoleAppenderConfigurator;
function Done: ILoggerProBuilder;
+ function CreateAppender: ILogAppender; override;
end;
{ Simple console appender configurator }
@@ -312,6 +322,7 @@ TSimpleConsoleAppenderConfigurator = class(TBaseAppenderConfigurator, ISimpleC
public
function WithLogLevel(aLogLevel: TLogType): ISimpleConsoleAppenderConfigurator;
function Done: ILoggerProBuilder;
+ function CreateAppender: ILogAppender; override;
end;
{ File appender configurator }
@@ -332,6 +343,7 @@ TFileAppenderConfigurator = class(TBaseAppenderConfigurator, IFileAppenderConf
function WithEncoding(aEncoding: TEncoding): IFileAppenderConfigurator;
function WithRenderer(aRenderer: ILogItemRenderer): IFileAppenderConfigurator;
function Done: ILoggerProBuilder;
+ function CreateAppender: ILogAppender; override;
end;
{ JSONL file appender configurator }
@@ -451,6 +463,7 @@ TOutputDebugStringAppenderConfigurator = class(TBaseAppenderConfigurator, IOut
function WithLogLevel(aLogLevel: TLogType): IOutputDebugStringAppenderConfigurator;
function WithRenderer(aRenderer: ILogItemRenderer): IOutputDebugStringAppenderConfigurator;
function Done: ILoggerProBuilder;
+ function CreateAppender: ILogAppender; override;
end;
{ UDP Syslog appender configurator }
@@ -558,6 +571,16 @@ TFilteredAppenderConfigurator = class(TBaseAppenderConfigurator, IFilteredAppe
function Done: ILoggerProBuilder;
end;
+ { Masking appender configurator }
+ TMaskingAppenderConfigurator = class(TBaseAppenderConfigurator, IMaskingAppenderConfigurator)
+ private
+ FInnerAppender: ILogAppender;
+ public
+ constructor Create(aBuilder: TLoggerProBuilder; aAppender: ILogAppender);
+ function WithLogLevel(aLogLevel: TLogType): IMaskingAppenderConfigurator;
+ function Done: ILoggerProBuilder;
+ end;
+
{ Builder implementation - hidden from interface }
TLoggerProBuilder = class(TInterfacedObject, ILoggerProBuilder)
private
@@ -637,6 +660,14 @@ function TBaseAppenderConfigurator.GetRenderer: ILogItemRenderer;
Result := FBuilder.GetDefaultRenderer;
end;
+function TBaseAppenderConfigurator.WithMasking: IMaskingAppenderConfigurator;
+var
+ LAppender: ILogAppender;
+begin
+ LAppender := CreateAppender;
+ Result := TMaskingAppenderConfigurator.Create(FBuilder, LAppender);
+end;
+
{ TLoggerProBuilder }
constructor TLoggerProBuilder.Create;
@@ -865,6 +896,12 @@ function TConsoleAppenderConfigurator.Done: ILoggerProBuilder;
Result := FBuilder;
end;
+function TConsoleAppenderConfigurator.CreateAppender: ILogAppender;
+begin
+ Result := TLoggerProConsoleAppender.Create(GetRenderer);
+ ApplyLogLevel(Result);
+end;
+
{ TSimpleConsoleAppenderConfigurator }
function TSimpleConsoleAppenderConfigurator.WithLogLevel(aLogLevel: TLogType): ISimpleConsoleAppenderConfigurator;
@@ -884,6 +921,12 @@ function TSimpleConsoleAppenderConfigurator.Done: ILoggerProBuilder;
Result := FBuilder;
end;
+function TSimpleConsoleAppenderConfigurator.CreateAppender: ILogAppender;
+begin
+ Result := TLoggerProSimpleConsoleAppender.Create;
+ ApplyLogLevel(Result);
+end;
+
{ TFileAppenderConfigurator }
constructor TFileAppenderConfigurator.Create(aBuilder: TLoggerProBuilder);
@@ -960,6 +1003,24 @@ function TFileAppenderConfigurator.Done: ILoggerProBuilder;
Result := FBuilder;
end;
+function TFileAppenderConfigurator.CreateAppender: ILogAppender;
+var
+ lFileNameFormat: string;
+begin
+ if FFileBaseName.IsEmpty then
+ lFileNameFormat := TLoggerProFileAppenderBase.DEFAULT_FILENAME_FORMAT
+ else
+ lFileNameFormat := FFileBaseName + '.{number}.{tag}.log';
+ Result := TLoggerProFileAppender.Create(
+ FMaxBackupFiles,
+ FMaxFileSizeInKB,
+ FLogsFolder,
+ lFileNameFormat,
+ GetRenderer,
+ FEncoding);
+ ApplyLogLevel(Result);
+end;
+
{ TJSONLFileAppenderConfigurator }
constructor TJSONLFileAppenderConfigurator.Create(aBuilder: TLoggerProBuilder);
@@ -1354,6 +1415,12 @@ function TOutputDebugStringAppenderConfigurator.Done: ILoggerProBuilder;
Result := FBuilder;
end;
+function TOutputDebugStringAppenderConfigurator.CreateAppender: ILogAppender;
+begin
+ Result := TLoggerProOutputDebugStringAppender.Create(GetRenderer);
+ ApplyLogLevel(Result);
+end;
+
{ TUDPSyslogAppenderConfigurator }
constructor TUDPSyslogAppenderConfigurator.Create(aBuilder: TLoggerProBuilder);
@@ -1659,6 +1726,31 @@ function TFilteredAppenderConfigurator.Done: ILoggerProBuilder;
Result := FBuilder;
end;
+{ TMaskingAppenderConfigurator }
+
+constructor TMaskingAppenderConfigurator.Create(aBuilder: TLoggerProBuilder; aAppender: ILogAppender);
+begin
+ inherited Create(aBuilder);
+ FInnerAppender := aAppender;
+end;
+
+function TMaskingAppenderConfigurator.WithLogLevel(aLogLevel: TLogType): IMaskingAppenderConfigurator;
+begin
+ FLogLevel := aLogLevel;
+ FLogLevelSet := True;
+ Result := Self;
+end;
+
+function TMaskingAppenderConfigurator.Done: ILoggerProBuilder;
+var
+ lMaskingAppender: ILogAppender;
+begin
+ lMaskingAppender := TLoggerProMaskingAppender.Create(FInnerAppender);
+ ApplyLogLevel(lMaskingAppender);
+ FBuilder.InternalAddAppender(lMaskingAppender);
+ Result := FBuilder;
+end;
+
{ Helper function }
function LoggerProBuilder: ILoggerProBuilder;
diff --git a/LoggerPro.MaskingAppender.pas b/LoggerPro.MaskingAppender.pas
new file mode 100644
index 0000000..311db9a
--- /dev/null
+++ b/LoggerPro.MaskingAppender.pas
@@ -0,0 +1,183 @@
+// *************************************************************************** }
+//
+// LoggerPro
+//
+// Copyright (c) 2010-2026 Daniele Teti
+//
+// https://github.com/danieleteti/loggerpro
+//
+// ***************************************************************************
+
+unit LoggerPro.MaskingAppender;
+
+interface
+
+uses
+ System.Classes,
+ System.SysUtils,
+ System.RegularExpressions,
+ LoggerPro;
+
+type
+ ///
+ /// 脱敏日志装饰器,用于对日志正文进行脱敏处理
+ /// 隐藏 11 位中国手机号中间 4 位(如 138****5678)
+ /// 隐藏 password=xxx 后面的明文值
+ ///
+ TLoggerProMaskingAppender = class(TLoggerProAppenderBase)
+ private
+ FInnerAppender: ILogAppender;
+ FPhoneRegex: TRegEx;
+ FPasswordRegex: TRegEx;
+ protected
+ function MaskPhoneNumber(const AMessage: string): string;
+ function MaskPassword(const AMessage: string): string;
+ function MaskMessage(const AMessage: string): string;
+ public
+ ///
+ /// 创建脱敏装饰器实例
+ ///
+ /// 被装饰的内部日志追加器
+ constructor Create(AInnerAppender: ILogAppender); reintroduce; virtual;
+
+ procedure Setup; override;
+ procedure TearDown; override;
+ procedure WriteLog(const aLogItem: TLogItem); override;
+ procedure SetEnabled(const Value: Boolean); override;
+ function IsEnabled: Boolean; override;
+ procedure SetLogLevel(const Value: TLogType); override;
+ function GetLogLevel: TLogType; override;
+ procedure TryToRestart(var Restarted: Boolean); override;
+ procedure SetLastErrorTimeStamp(const LastErrorTimeStamp: TDateTime); override;
+ function GetLastErrorTimeStamp: TDateTime; override;
+ end;
+
+implementation
+
+{ TLoggerProMaskingAppender }
+
+constructor TLoggerProMaskingAppender.Create(AInnerAppender: ILogAppender);
+begin
+ inherited Create;
+ FInnerAppender := AInnerAppender;
+
+ // 预编译正则表达式,避免在高并发日志下产生性能瓶颈
+ // 中国手机号正则:11位数字,以1开头,第二位是3-9
+ FPhoneRegex := TRegEx.Create('(\b1[3-9]\d{1})(\d{4})(\d{4}\b)', [roCompiled]);
+ // 密码参数正则:password=后面的值
+ FPasswordRegex := TRegEx.Create('(password=)([^\s&]*)', [roCompiled, roIgnoreCase]);
+end;
+
+function TLoggerProMaskingAppender.MaskPhoneNumber(const AMessage: string): string;
+begin
+ // 使用预编译的正则表达式替换手机号中间4位为****
+ Result := FPhoneRegex.Replace(AMessage, '$1****$3');
+end;
+
+function TLoggerProMaskingAppender.MaskPassword(const AMessage: string): string;
+begin
+ // 使用预编译的正则表达式替换password=后面的值为****
+ Result := FPasswordRegex.Replace(AMessage, '$1****');
+end;
+
+function TLoggerProMaskingAppender.MaskMessage(const AMessage: string): string;
+begin
+ Result := AMessage;
+
+ // 先处理密码脱敏,再处理手机号脱敏
+ Result := MaskPassword(Result);
+ Result := MaskPhoneNumber(Result);
+end;
+
+procedure TLoggerProMaskingAppender.Setup;
+begin
+ if Assigned(FInnerAppender) then
+ FInnerAppender.Setup;
+end;
+
+procedure TLoggerProMaskingAppender.TearDown;
+begin
+ if Assigned(FInnerAppender) then
+ FInnerAppender.TearDown;
+end;
+
+procedure TLoggerProMaskingAppender.WriteLog(const aLogItem: TLogItem);
+var
+ LMaskedLogItem: TLogItem;
+ LMaskedMessage: string;
+begin
+ if not Assigned(FInnerAppender) then
+ Exit;
+
+ // 对日志消息进行脱敏处理
+ LMaskedMessage := MaskMessage(aLogItem.LogMessage);
+
+ // 创建脱敏后的日志项
+ LMaskedLogItem := TLogItem.Create(
+ aLogItem.LogType,
+ LMaskedMessage,
+ aLogItem.LogTag,
+ aLogItem.TimeStamp,
+ aLogItem.ThreadID,
+ aLogItem.Context
+ );
+
+ try
+ // 将脱敏后的日志项传递给内部追加器
+ FInnerAppender.WriteLog(LMaskedLogItem);
+ finally
+ LMaskedLogItem.Free;
+ end;
+end;
+
+procedure TLoggerProMaskingAppender.SetEnabled(const Value: Boolean);
+begin
+ if Assigned(FInnerAppender) then
+ FInnerAppender.SetEnabled(Value);
+end;
+
+function TLoggerProMaskingAppender.IsEnabled: Boolean;
+begin
+ if Assigned(FInnerAppender) then
+ Result := FInnerAppender.IsEnabled
+ else
+ Result := False;
+end;
+
+procedure TLoggerProMaskingAppender.SetLogLevel(const Value: TLogType);
+begin
+ if Assigned(FInnerAppender) then
+ FInnerAppender.SetLogLevel(Value);
+end;
+
+function TLoggerProMaskingAppender.GetLogLevel: TLogType;
+begin
+ if Assigned(FInnerAppender) then
+ Result := FInnerAppender.GetLogLevel
+ else
+ Result := TLogType.Debug;
+end;
+
+procedure TLoggerProMaskingAppender.TryToRestart(var Restarted: Boolean);
+begin
+ if Assigned(FInnerAppender) then
+ FInnerAppender.TryToRestart(Restarted)
+ else
+ Restarted := False;
+end;
+
+procedure TLoggerProMaskingAppender.SetLastErrorTimeStamp(const LastErrorTimeStamp: TDateTime);
+begin
+ if Assigned(FInnerAppender) then
+ FInnerAppender.SetLastErrorTimeStamp(LastErrorTimeStamp);
+end;
+
+function TLoggerProMaskingAppender.GetLastErrorTimeStamp: TDateTime;
+begin
+ if Assigned(FInnerAppender) then
+ Result := FInnerAppender.GetLastErrorTimeStamp
+ else
+ Result := 0;
+end;
+
+end.
\ No newline at end of file
diff --git a/TestMaskingAppender.dpr b/TestMaskingAppender.dpr
new file mode 100644
index 0000000..6b6bc91
--- /dev/null
+++ b/TestMaskingAppender.dpr
@@ -0,0 +1,42 @@
+program TestMaskingAppender;
+
+{$APPTYPE CONSOLE}
+
+uses
+ System.SysUtils,
+ LoggerPro,
+ LoggerPro.ConsoleAppender,
+ LoggerPro.MaskingAppender,
+ LoggerPro.Builder;
+
+var
+ Log: ILogWriter;
+
+begin
+ try
+ // 创建带有脱敏功能的日志记录器
+ Log := LoggerProBuilder
+ .WithDefaultLogLevel(TLogType.Debug)
+ .WriteToConsole
+ .WithMasking
+ .Done
+ .Build;
+
+ // 测试手机号脱敏
+ Log.Debug('用户登录,手机号:13812345678');
+ Log.Info('新用户注册,手机号:15987654321');
+
+ // 测试密码脱敏
+ Log.Warn('登录失败,用户名:admin,password=mypassword123');
+ Log.Error('认证错误,password=admin123&username=test');
+
+ // 测试混合内容脱敏
+ Log.Info('用户信息:手机号=18611112222,password=userpass456');
+
+ WriteLn('脱敏测试完成,请查看上面的日志输出');
+ ReadLn;
+ except
+ on E: Exception do
+ Writeln(E.ClassName, ': ', E.Message);
+ end;
+end.
\ No newline at end of file
diff --git a/samples/10_multiple_appenders/LoggerProConfig.pas b/samples/10_multiple_appenders/LoggerProConfig.pas
index c9f6019..1a03543 100644
--- a/samples/10_multiple_appenders/LoggerProConfig.pas
+++ b/samples/10_multiple_appenders/LoggerProConfig.pas
@@ -13,6 +13,7 @@ implementation
LoggerPro.FileAppender,
LoggerPro.ConsoleAppender,
LoggerPro.OutputDebugStringAppender,
+ LoggerPro.MaskingAppender,
LoggerPro.Builder;
var
@@ -38,12 +39,19 @@ procedure SetupLogger;
// - Multiple appenders (File, Console, OutputDebugString)
// - Conditional log level based on DEBUG/RELEASE build
// - WithDefaultLogLevel to set minimum level for all appenders
+ // - 使用 TLoggerProMaskingAppender 装饰器进行脱敏处理
//
_Log := LoggerProBuilder
.WithDefaultLogLevel(LOG_LEVEL)
- .WriteToFile.Done
- .WriteToConsole.Done
- .WriteToOutputDebugString.Done
+ .WriteToFile
+ .WithMasking // 添加脱敏装饰器
+ .Done
+ .WriteToConsole
+ .WithMasking // 添加脱敏装饰器
+ .Done
+ .WriteToOutputDebugString
+ .WithMasking // 添加脱敏装饰器
+ .Done
.Build;
// ============================================================================