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; // ============================================================================