整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          系統日志收集之初探rsyslog

          系統日志收集之初探rsyslog

          統內核和許多程序會產生各種錯誤信息、警告信息和其他的提示信息,這些信息對用戶了解系統的運行狀態是非常有用的,所以需要把它們保存到對應的日志文件中,以便后續進行分析和監控系統或軟件的狀態。 Linux 系統擁有非常靈活和強大的日志功能,幾乎可以保存所有的操作記錄,并可以從中檢索出我們需要的信息。完成這個工作的守護進程就是 rsyslog。

          介紹

          談及 rsyslog[1],就不可避免的涉及另外兩個軟件 syslog[2]syslog-ng[3]。這三款軟件設計的目標是一樣的,就是解決系統和程序的日志收集問題。每一個項目都在試圖提高前者的穩定性和功能性。

          syslog

          syslog 由 Eric Allman 在 1980 年代開發,是 Sendmail 項目的一部分。因為它的易用性被很多類 Unix 系統使用,成為其標準的日志記錄解決方案。在這個過程中,它演變成一種協議,成為在互聯網協議(TCP/IP)的網絡中傳遞記錄檔消息的標準。

          syslog 協議屬于一種主從式協議:syslog 發送端會發送出一個小的文字消息(小于 1024 位組)到 syslog 接收端。接收端通常名為 syslogd、syslog daemon 或 syslog 服務器。系統日志消息可以被以 UDP 協議或 TCP 協議來發送。這些資料是以明碼類型被發送。不過由于 SSL 加密外套(例如 Stunnel、sslio 或 sslwrap 等)并非 syslog 協議本身的一部分,因此可以被用來透過SSL/TLS 方式提供一層加密。

          syslog-ng

          syslog-ng 是 syslog NextGeneration 的簡寫。該項目發起于 1998 年并基于 syslog 協議開發。作為 syslog 的下一代產品,功能肯定比 syslog 強大的多,如高性能,可靠的傳輸,支持多平臺,高可靠性,眾多的用戶群體,強大的日志過濾及排序,事件標簽和關聯性,支持最新的IETF標準等。

          rsyslog

          rsyslog 是 the rocket-fast system for log processing 的簡寫。該項目始于 2004 年,當時 rsyslog 的主要作者 Rainer Gerhards 決定編寫一個新的強大 syslog 守護程序來與 syslog-ng 競爭。它實現了基本的 syslog 協議,并擴展了基于內容的過濾功能,豐富了過濾能力,處理脫機輸出的隊列操作,支持不同模塊的輸出,配置選項更加靈活,并添加了使用 TCP 進行傳輸的功能。

          由于 rsyslog 的高性能,出色的安全性和模塊化設計,它不僅作為常規的系統日志收集工具,還能夠接受各種來源的輸入,將其轉換,然后將結果輸出到不同的目的地。

          它的優勢有如下:

          ? 高性能(使用 C 編寫,運用多線程)

          ? 支持 TCP, SSL, TLS, RELP

          ? 支持多種輸出(MySQL, PostgreSQL等)

          ? 支持對系統日志的過濾

          ? 靈活配置多種輸出

          正因為如此,它是眾多類 Unix 系統和 GNU/Linux 發行版系統日志采集的首選。

          syslog 協議

          既然三款軟件都基于 syslog 協議,那就先來介紹一下 syslog 協議。相關 RFC 文件有 2001 年發行的RFC 3164[4](The BSD syslog Protocol), 2009 年發行的 RFC 5424[5](The Syslog Protocol), RFC 5425[6](Transport Layer Security Mapping for Syslog),RFC 5426[7](Transmission of Syslog Messages over UDP)。其中RFC 3164 已經被 RFC 5424 廢除,所以下面介紹的以 RFC 5424 為準。

          三層模型

          Syslog 協議使用三層結構,第一層是消息層,指要傳輸的信息;第二層是應用層,主要用于消息的生成,解析,路由和存儲,代表有發送者,中繼器和接受者。第三層是傳輸層,主要用于發送和接收網絡上的信息,代表有發送設備和接收設備。 具體的層次結構見下圖(這塊的理解可以參考 OSI 7層模型或 TCP/IP 4層模型):

          部署場景

          syslog 協議遵循以下的原則:

          ? 協議沒有信息確認機制 消息從發送者發送到接受者的 UDP 514 端口,不需要接收方應答。

          ? 發送者和中繼器可以將相同的消息發送給多個接收者和中繼器

          ? 發送者,中繼器和接收者可以部署在同一個系統上。

          按照上述原則,有如下圖的部署場景。

          消息格式

          syslog 協議定義了消息格式,由三部分組成:消息頭 HEADER ,結構化數據 STRUCTURED-DATA 和消息 MSG(可選)。其中消息頭又包含優先級(PRIority),版本號(VERSION),時間戳(TIMESTAMP),主機名(HOSTNAME), 應用名(APP-NAME),進程標識(PROCID)和消息標識(MSGID)。

          其中優先級由設備(Facility)和嚴重性(Severity)共同決定。PRI=Facility * 8 + Severity。 設備的可選值有以下24個:

          代號

          設備(Facility)

          注釋

          0

          kernel messages

          內核相關

          1

          user-level messages

          用戶相關(默認)

          2

          mail system

          郵件相關

          3

          system daemons

          系統守護進程相關

          4

          security/authorization messages (note 1)

          登陸授權相關

          5

          messages generated internally by syslogd

          syslogd相關

          6

          line printer subsystem

          打印相關

          7

          network news subsystem

          新聞相關

          8

          UUCP subsystem

          unix到unix的cp相關

          9

          clock daemon (note 2)

          任務計劃相關

          10

          security/authorization messages (note 1)

          登陸授權相關

          11

          FTP daemon

          FTP相關

          12

          NTP subsystem


          13

          log audit (note 1)

          登陸授權相關

          14

          log alert (note 1)

          登陸授權相關

          15

          clock daemon (note 2)

          任務計劃相關

          16

          local use 0 (local0)

          用戶自定義0

          17

          local use 1 (local1)

          用戶自定義1

          18

          local use 2 (local2)

          用戶自定義2

          19

          local use 3 (local3)

          用戶自定義3

          20

          local use 4 (local4)

          用戶自定義4

          21

          local use 5 (local5)

          用戶自定義5

          22

          local use 6 (local6)

          用戶自定義6

          23

          local use 7 (local7)

          用戶自定義7

          嚴重性的可選值有以下8個,這也是Facility * 8的原因:

          代號

          嚴重性(Severity)

          注釋

          0

          Emergency: system is unusable

          崩潰級別

          1

          Alert: action must be taken immediately

          報警級別

          2

          Critical: critical conditions

          危急級別

          3

          Error: error conditions

          錯誤級別

          4

          Warning: warning conditions

          警告級別

          5

          Notice: normal but significant condition

          提示級別

          6

          Informational: informational messages

          消息級別

          7

          Debug: debug-level messages

          調試級別

          其它注意點

          ? 協議的實現必須支持基于 TLS 的傳輸,應該支持基于 UDP 的傳輸

          ? 所有接收設備必須能夠結構長度不超過480個八位字節的消息,應該接收長度最大為2048個八位字節的消息,可以接收超過2048個八位字節的消息(可以截斷或丟棄)。

          rsyslog 使用

          鑒于 rsyslog 已經是眾多類 Unix 系統和 GNU/Linux 發行版系統日志采集的首選,所以這里重點介紹一下 rsyslog 使用。

          配置文件

          一般 rsyslog 的配置文件在 /etc/rsyslog.conf, 其由 3 個部分組成:模塊(MODULES),全局設置(GLOBAL DRICTIVES)和規則(RULE)。這里以 Centos 中 rsyslog 配置為例。

          # rsyslog configuration file
          
          # For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html
          # If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html
          
          #### 模塊MODULES ####
          
          # The imjournal module bellow is now used as a message source instead of imuxsock.
          $ModLoad imuxsock # provides support for local system logging (e.g. via logger command)
          $ModLoad imjournal # provides access to the systemd journal
          #$ModLoad imklog # reads kernel messages (the same are read from journald)
          #$ModLoad immark  # provides --MARK-- message capability
          
          # Provides UDP syslog reception
          #$ModLoad imudp
          #$UDPServerRun 514
          
          # Provides TCP syslog reception
          #$ModLoad imtcp
          #$InputTCPServerRun 514
          
          
          #### 全局設置GLOBAL DIRECTIVES ####
          
          # Where to place auxiliary files
          $WorkDirectory /var/lib/rsyslog
          
          # Use default timestamp format
          $ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
          
          # File syncing capability is disabled by default. This feature is usually not required,
          # not useful and an extreme performance hit
          #$ActionFileEnableSync on
          
          # Include all config files in /etc/rsyslog.d/
          $IncludeConfig /etc/rsyslog.d/*.conf
          
          # Turn off message reception via local log socket;
          # local messages are retrieved through imjournal now.
          $OmitLocalLogging on
          
          # File to store the position in the journal
          $IMJournalStateFile imjournal.state
          
          
          #### 規則RULES ####
          
          # Log all kernel messages to the console.
          # Logging much else clutters up the screen.
          #kern.*                                                 /dev/console
          
          # Log anything (except mail) of level info or higher.
          # Don't log private authentication messages!
          *.info;mail.none;authpriv.none;cron.none                /var/log/messages
          
          # The authpriv file has restricted access.
          authpriv.*                                              /var/log/secure
          
          # Log all the mail messages in one place.
          mail.*                                                  -/var/log/maillog
          
          
          # Log cron stuff
          cron.*                                                  /var/log/cron
          
          # Everybody gets emergency messages
          *.emerg                                                 :omusrmsg:*
          
          # Save news errors of level crit and higher in a special file.
          uucp,news.crit                                          /var/log/spooler
          
          # Save boot messages also to boot.log
          local7.*                                                /var/log/boot.log
          
          
          # ### begin forwarding rule ###
          # The statement between the begin ... end define a SINGLE forwarding
          # rule. They belong together, do NOT split them. If you create multiple
          # forwarding rules, duplicate the whole block!
          # Remote Logging (we use TCP for reliable delivery)
          #
          # An on-disk queue is created for this action. If the remote host is
          # down, messages are spooled to disk and sent when it is up again.
          #$ActionQueueFileName fwdRule1 # unique name prefix for spool files
          #$ActionQueueMaxDiskSpace 1g   # 1gb space limit (use as much as possible)
          #$ActionQueueSaveOnShutdown on # save messages to disk on shutdown
          #$ActionQueueType LinkedList   # run asynchronously
          #$ActionResumeRetryCount -1    # infinite retries if host is down
          # remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional
          #*.* @@remote-host:514
          # ### end of the forwarding rule ###

          每次修改配置文件后可以通過以下操作來判斷配置文件是否合理并重啟 rsyslogd 服務。

          # 驗證配置文件 /etc/syslog.conf 是否合理
          rsyslogd -f /etc/rsyslog.conf -N1
          # 重啟 rsyslog 服務
          systemctl restart rsyslog

          基本驗證

          一般系統都會提供 logger 命令行,可以使用其向系統輸入日志。

          # -i 記錄進程id
          # -t 標識記錄的tag
          # -p 指定消息的設備信息和日志等級,默認user.info
          logger -i -t 'hjy_test' -p facility.level 'message'

          實戰

          網上有一篇博文[8]是介紹將 rsyslog 的日志輸出到 mysql 中,當然強大的 rsyslog 不僅支持輸出到 mysql 中,基本覆蓋了所有的主流存儲軟件。 不過這里介紹的實戰是利用 rsyslog 的用戶自定義設備來實現推薦系統的用戶行為收集。大體的思路是推薦請求或數據上報請求通過 nginx 將請求轉發到多臺 online 服務中的一個。 該服務處理完請求后會將推薦的數據或打點上報的數據通過 SyslogHandler 匯總到目標服務器上進行推薦系統用戶行為的統一處理。具體架構見下圖:

          優勢如下:

          ? 速度快,穩定性高,性能好(支持百萬QPS,壓測到十萬級別[9])

          ? rsyslogd 一般服務器自帶,不需要安裝,只需簡單配置

          ? 客戶端實現簡單,比如 Python 中 logger 就有 SyslogHandler 來向 rsyslogd 發送日志。

          參考文獻

          1.rsyslog官網[10]

          2.rsyslog源碼[11]

          3.維基百科syslog[12]

          4.維基百科rsyslog[13]

          5.維基百科syslog-ng[14]

          6.三種syslog比較[15]

          7.rfc3164[16]

          8.rfc5424[17]

          9.rfc5425[18]

          10.rfc5426[19]

          11.rsyslog配置[20]

          12.記錄rsyslog日志到mysql[21]

          13.rsyslog 的 TCP 轉發性能測試[22]

          References

          [1] rsyslog: https://en.wikipedia.org/wiki/Rsyslog
          [2] syslog:
          https://zh.wikipedia.org/wiki/Syslog
          [3] syslog-ng:
          https://en.wikipedia.org/wiki/Syslog-ng
          [4] RFC 3164:
          https://tools.ietf.org/html/rfc3164
          [5] RFC 5424:
          https://tools.ietf.org/html/rfc5424
          [6] RFC 5425:
          https://tools.ietf.org/html/rfc5425
          [7] RFC 5426:
          https://tools.ietf.org/html/rfc5426
          [8] 博文:
          https://www.codenong.com/cs105581064/
          [9] 壓測到十萬級別:
          http://chenlinux.com/2015/02/12/rsyslog-forwarder-testing/

          NET 中的日志使用技巧

          Serilog

          Serilog 是 .NET 社區中使用最廣泛的日志框架,所以筆者使用一個小節單獨講解使用方法。

          示例項目在 Demo2.Console 中。

          創建一個控制臺程序,引入兩個包:

          Serilog.Sinks.Console
          Serilog.Sinks.File

          除此之外,還有 Serilog.Sinks.ElasticsearchSerilog.Sinks.RabbitMQ 等。Serilog 提供了用于將日志事件以各種格式寫入存儲的接收器。下面列出的許多接收器都是由更廣泛的 Serilog 社區開發和支持的;https://github.com/serilog/serilog/wiki/Provided-Sinks

          可以直接使用代碼配置 Serilog:

          private static Serilog.ILogger GetLogger()
          {
          const string LogTemplate="{SourceContext} {Scope} {Timestamp:HH:mm} [{Level}] {Message:lj} {Properties:j} {NewLine}{Exception}";
          var logger=new LoggerConfiguration()
          .Enrich.WithMachineName()
          .Enrich.WithThreadId()
          .Enrich.FromLogContext()
          #if DEBUG
          .MinimumLevel.Debug()
          #else
          .MinimumLevel.Information()
          #endif
          .WriteTo.Console(outputTemplate: LogTemplate)
          .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day, outputTemplate: LogTemplate)
          .CreateLogger();
          return logger;
          }

          如果想從配置文件中加載,添加 Serilog.Settings.Configuration:

          private static Serilog.ILogger GetJsonLogger()
          {
          IConfiguration configuration=new ConfigurationBuilder()
          .SetBasePath(AppContext.BaseDirectory)
          .AddJsonFile(path: "serilog.json", optional: true, reloadOnChange: true)
          .Build();
          if (configuration==)
          {
          throw new ArgumentException($"未能找到 serilog.json 日志配置文件");
          }
          var logger=new LoggerConfiguration()
          .ReadFrom.Configuration(configuration)
          .CreateLogger();
          return logger;
          }

          serilog.json 配置文件示例:

          {
          "Serilog": {
          "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
          "MinimumLevel": {
          "Default": "Debug"
          },
          "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
          "WriteTo": [
          {
          "Name": "Console",
          "Args": {
          "outputTemplate": "{SourceContext} {Scope} {Timestamp:HH:mm} [{Level}] {Message:lj} {Properties:j} {NewLine}{Exception}"
          }
          },
          {
          "Name": "File",
          "Args": {
          "path": "logs/log-.txt",
          "rollingInterval": "Day",
          "outputTemplate": "{SourceContext} {Scope} {Timestamp:HH:mm} [{Level}] {Message:lj} {Properties:j} {NewLine}{Exception}"
          }
          }
          ]
          }
          }

          依賴注入 Serilog。

          引入 Serilog.Extensions.Logging 包。

          private static Microsoft.Extensions.Logging.ILogger InjectLogger()
          {
          var logger=GetJsonLogger();
          var ioc=new ServiceCollection();
          ioc.AddLogging(builder=> builder.AddSerilog(logger: logger, dispose: true));
          var loggerProvider=ioc.BuildServiceProvider().GetRequiredService<ILoggerProvider>();
          return loggerProvider.CreateLogger("Program");
          }

          最后,使用不同方式配置 Serilog 日志,然后啟動程序打印日志。

          static void Main()
          {
          var log1=GetLogger();
          log1.Debug("溪源More、癡者工良");
          var log2=GetJsonLogger();
          log2.Debug("溪源More、癡者工良");
          var log3=InjectLogger();
          log3.LogDebug("溪源More、癡者工良");
          }
          20:50 [Debug] 溪源More、癡者工良 {"MachineName": "WIN-KQDULADM5LA", "ThreadId": 1}
          20:50 [Debug] 溪源More、癡者工良 {"MachineName": "WIN-KQDULADM5LA", "ThreadId": 1}
          20:50 [Debug] 溪源More、癡者工良 {"MachineName": "WIN-KQDULADM5LA", "ThreadId": 1}

          在 ASP.NET Core 中使用日志

          示例項目在 Demo2.Api 中。

          新建一個 ASP.NET Core API 新項目,引入 Serilog.AspNetCore 包。

          在 Program 中添加代碼注入 Serilog 。

          var builder=WebApplication.CreateBuilder(args);

          Log.Logger=new LoggerConfiguration()
          .ReadFrom.Configuration(builder.Configuration)
          .CreateLogger();
          builder.Host.UseSerilog(Log.Logger);
          //builder.Host.UseSerilog();

          將前面示例中的 serilog.json 文件內容復制到 appsettings.json 中。

          啟動程序后,嘗試訪問 API 接口,會打印示例如下的日志:

          Microsoft.AspNetCore.Hosting.Diagnostics 20:32 [Information] Request finished HTTP/1.1 GET http://localhost:5148/WeatherForecast - - - 200 - application/json;+charset=utf-8 1029.4319ms {"ElapsedMilliseconds": 1029.4319, "StatusCode": 200, "ContentType": "application/json; charset=utf-8", "ContentLength": , "Protocol": "HTTP/1.1", "Method": "GET", "Scheme": "http", "Host": "localhost:5148", "PathBase": "", "Path": "/WeatherForecast", "QueryString": "", "EventId": {"Id": 2}, "RequestId": "0HMOONQO5ONKU:00000003", "RequestPath": "/WeatherForecast", "ConnectionId": "0HMOONQO5ONKU"}

          如果需要為請求上下文添加一些屬性信息,可以添加一個中間件,示例如下:

          app.UseSerilogRequestLogging(options=>
          {
          options.EnrichDiagnosticContext=(diagnosticContext, httpContext)=>
          {
          diagnosticContext.Set("TraceId", httpContext.TraceIdentifier);
          };
          });
           HTTP GET /WeatherForecast responded 200 in 181.9992 ms {"TraceId": "0HMSD1OUG2DHG:00000003" ... ...

          對請求上下文添加屬性信息,比如當前請求的用戶信息,在本次請求作用域中使用日志打印信息時,日志會包含這些上下文信息,這對于分析日志還有幫助,可以很容易分析日志中那些條目是同一個上下文。在微服務場景下,會使用 ElasticSearch 等日志存儲引擎查詢分析日志,如果在日志中添加了相關的上下文屬性,那么在分析日志時可以通過對應的屬性查詢出來,分析日志時可以幫助排除故障。

          如果需要打印 http 的請求和響應日志,我們可以使用 ASP.NET Core 自帶的 HttpLoggingMiddleware 中間件。

          首先注入請求日志攔截服務。

          builder.Services.AddHttpLogging(logging=>
          {
          logging.LoggingFields=HttpLoggingFields.All;
          // 避免打印大量的請求和響應內容,只打印 4kb
          logging.RequestBodyLogLimit=4096;
          logging.ResponseBodyLogLimit=4096;
          });

          通過組合 HttpLoggingFields 枚舉,可以配置中間件打印 Request、Query、HttpMethod、Header、Response 等信息。

          可以將HttpLogging 中間件放在 Swagger、Static 之后,這樣的話可以避免打印哪些用處不大的請求,只保留 API 請求相關的日志。

          app.UseHttpLogging();

          HttpLoggingMiddleware 中的日志模式是以 Information 級別打印的,在項目上線之后,如果每個請求都被打印信息的話,會降低系統性能,因此我們可以在配置文件中覆蓋配置,避免打印普通的日志。

          "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"

          上下文屬性和作用域

          示例項目在 Demo2.ScopeLog 中。

          日志范圍注意事項
          Microsoft.Extensions.Logging.Abstractions 提供 BeginScopeAPI,可用于添加任意屬性以記錄特定代碼區域內的事件。

          解釋其作用

          API 有兩種形式:

          IDisposable BeginScope<TState>(TState state)
          IDisposable BeginScope(this ILogger logger, string messageFormat, params object[] args)

          使用如下的模板:

          {SourceContext} {Timestamp:HH:mm} [{Level}] (ThreadId:{ThreadId}) {Message}{NewLine}{Exception} {Scope}

          使用示例:

           static void Main()
          {
          var logger=GetLogger();
          using (logger.BeginScope("Checking mail"))
          {
          // Scope is "Checking mail"
          logger.LogInformation("Opening SMTP connection");

          using (logger.BeginScope("Downloading messages"))
          {
          // Scope is "Checking mail" -> "Downloading messages"
          logger.LogError("Connection interrupted");
          }
          }
          }

          而在 Serilog 中,除了支持上述接口外,還通過 LogContext 提供了在日志中注入上下文屬性的方法。其作用是添加屬性之后,使得在其作用域之內打印日志時,日志會攜帶這些上下文屬性信息。

           using (LogContext.PushProperty("Test", 1))
          {
          // Process request; all logged events will carry `RequestId`
          Log.Information("{Test} Adding {Item} to cart {CartId}", 1,1);
          }

          嵌套復雜一些:

          using (LogContext.PushProperty("A", 1))
          {
          log.Information("Carries property A=1");

          using (LogContext.PushProperty("A", 2))
          using (LogContext.PushProperty("B", 1))
          {
          log.Information("Carries A=2 and B=1");
          }

          log.Information("Carries property A=1, again");
          }

          當需要設置大量屬性時,下面的方式會比較麻煩;

          using (LogContext.PushProperty("Test1", 1))
          using (LogContext.PushProperty("Test2", 2))
          {
          }

          例如在 ASP.NET Core 中間件中,我們可以批量添加:

           public async Task InvokeAsync(HttpContext context, RequestDelegate next)
          {
          var enrichers=new List<ILogEventEnricher>();
          if (!string.IsOrEmpty(correlationId))
          {
          enrichers.Add(new PropertyEnricher(_options.EnricherPropertyNames.CorrelationId, correlationId));
          }

          using (LogContext.Push(enrichers.ToArray()))
          {
          await next(context);
          }
          }

          在業務系統中,可以通過在中間件獲取 Token 中的用戶信息,然后注入到日志上下文中,這樣打印出來的日志,會攜帶用戶信息。

          非侵入式日志

          非侵入式的日志有多種方法,比如 ASP.NET Core 中間件管道,或者使用 AOP 框架。

          這里可以使用筆者開源的 CZGL.AOP 框架,Nuget 中可以搜索到。

          示例項目在 Demo2.AopLog 中。

          有一個類型,我們需要在執行 SayHello 之前和之后打印日志,將參數和返回值記錄下來。

           public class Hello
          {
          public virtual string SayHello(string content)
          {
          var str=$"Hello,{content}";
          return str;
          }
          }

          編寫統一的切入代碼,這些代碼將在函數被調用時執行。

          Before 會在被代理的方法執行前或被代理的屬性調用時生效,你可以通過 AspectContext 上下文,獲取、修改傳遞的參數。

          After 在方法執行后或屬性調用時生效,你可以通過上下文獲取、修改返回值。

          public class LogAttribute : ActionAttribute
          {
          public override void Before(AspectContext context)
          {
          Console.WriteLine($"{context.MethodInfo.Name} 函數被執行前");
          foreach (var item in context.MethodValues)
          Console.WriteLine(item.ToString());
          }

          public override object After(AspectContext context)
          {
          Console.WriteLine($"{context.MethodInfo.Name} 函數被執行后");
          Console.WriteLine(context.MethodResult.ToString());
          return context.MethodResult;
          }
          }

          改造 Hello 類,代碼如下:

          [Interceptor]
          public class Hello
          {
          [Log]
          public virtual string SayHello(string content)
          {
          var str=$"Hello,{content}";
          return str;
          }
          }

          然后創建代理類型:

           static void Main(string[] args)
          {
          Hello hello=AopInterceptor.CreateProxyOfClass<Hello>();
          hello.SayHello("any one");
          Console.Read();
          }

          啟動程序,會輸出:

          SayHello 函數被執行前
          any one
          SayHello 函數被執行后
          Hello,any one

          你完全不需要擔心 AOP 框架會給你的程序帶來性能問題,因為 CZGL.AOP 框架采用 EMIT 編寫,并且自帶緩存,當一個類型被代理過,之后無需重復生成。

          CZGL.AOP 可以通過 .NET Core 自帶的依賴注入框架和 Autofac 結合使用,自動代理 CI 容器中的服務。這樣不需要 AopInterceptor.CreateProxyOfClass 手動調用代理接口。

          CZGL.AOP 代碼是開源的,可以參考筆者另一篇博文:

          https://www.cnblogs.com/whuanle/p/13160139.html

          . Log4Net簡介

          Log4net是從Java中的Log4j遷移過來的一個.Net版的開源日志框架,它的功能很強大,可以將日志分為不同的等級,以不同的格式輸出到不同的存儲介質中,比如:數據庫、txt文件、內存緩沖區、郵件、控制臺、ANSI終端、遠程接收端等等,我們這里主要介紹最常用的兩種:txt文件和數據庫。

          (PS:其它的存儲介質詳見 http://logging.apache.org/log4net/release/config-examples.html)

          Log4net將日志分為五個級別,分別是: FATAL(致命錯誤) > ERROR(一般錯誤) > WARN(警告) > INFO(一般信息) > DEBUG(調試信息),每個級別都對應著一組重載方法進行調用。

          官網地址:http://logging.apache.org/log4net/index.html

          Nuget地址:https://www.nuget.org/packages/log4net/

          Nuget安裝:Install-Package log4net

          最新版本:2.0.8 (2018-08-09)

          本節主要圍繞兩個主要的存儲介質:【txt文件】和【SQLServer數據庫】展開,涵蓋的知識點有:

          ①. 基本的使用步驟。

          ②. 初始化關聯配置文件的幾種形式。

          ③. 代碼調用詳解。

          ④. 配置文件詳解。

          ⑤. 簡單的封裝和在MVC框架中的調用。

          二. 基本使用步驟

          我們先以控制臺程序為例,簡單介紹Log4net存儲日志到txt文本文檔中,后面在做代碼的詳解。

          1. 新建01-SimpleDemo控制臺程序,通過指令 【Install-Package log4net】安裝相應程序集。

          2. 在默認配置文件中App.config(B/S程序則為web.config)中進行配置,主要分兩塊:

          A. 在<configuration></configuration>節點下新增節點下新增(要在其最頂部):

          <configSections>

          <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />

          </configSections>

          B. 在<configuration></configuration>根節點下,配置log4net的核心配置代碼, 主要節點如下:

          <log4net> <appender> </appender> <root></root> </log4net>

          詳細代碼如下:

           1 <?xml version="1.0" encoding="utf-8" ?>
           2 <configuration>
           3 <!-- 1. 添加log4net的節點聲明配置代碼-->
           4 <configSections>
           5 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
           6 </configSections>
           7 <!--2. log4net的核心配置代碼-->
           8 <log4net> 
           9 <!--把日志信息輸出到以日期命名的文件里-->
          10 <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
          11 <!--文件夾的位置-->
          12 <file value="D:\MyLog1\" />
          13 <appendToFile value="true" />
          14 <!--動態生成文件名-->
          15 <param name="StaticLogFileName" value="false" />
          16 <!--以日期命名-->
          17 <param name="DatePattern" value="yyyyMMdd".log"" />
          18 <rollingStyle value="Date" />
          19 <!--日志在日志文件中的布局方式-->
          20 <layout type="log4net.Layout.PatternLayout">
          21 <conversionPattern value="%newline %n記錄時間:%date %n線程ID:[%thread] %n日志級別: %-5level %n出錯類:%logger property: [%property{NDC}] - %n錯誤描述:%message%newline %n"/>
          22 </layout>
          23 <!--使用最小鎖定模型(minimal locking model),以允許多個進程可以寫入同一個文件 -->
          24 <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
          25 </appender> 
          26 <root>
          27 <level value="ALL"></level>
          28 <appender-ref ref="RollingFileAppender"></appender-ref>
          29 </root> 
          30 </log4net>
          31 <startup>
          32 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
          33 </startup>
          34 </configuration>
          

          3. 代碼調用

          1 log4net.Config.XmlConfigurator.Configure();
          2 ILog log=LogManager.GetLogger("test");
          3 log.Debug("調試信息");
          

          4. 運行結果

          截止此處,日志保存成功。

          三. 初始化配置文件

           前面提到在默認配置文件中App.config(B/S程序則為web.config)中進行配置,可以通過代碼 log4net.Config.XmlConfigurator.Configure(); 來初始化配置,或者還可以通過 [assembly: log4net.Config.XmlConfigurator()] 反射的形式進行初始化配置,二者可以達到同樣的效果,代表了兩種初始化配置文件的形式。

          PS: [assembly: log4net.Config.XmlConfigurator()] 可以加在 當前使用文件的 namespace上作用于當前文件,或者加在Properties/AssemblyInfo.cs中,則該項目全局都無須再初始化了。

          在實際項目中,默認的配置文件里可能包含很多框架的信息,這個時候把 log4net的配置代碼再放入進去,就會顯得有點雜亂,或者有些“奇葩”的人把默認配置文件改名了,這個時候使用上述默認的兩種方式就不好用了,那么這種情況怎么處理呢?

          這里重點介紹 通過 log4net.Config.XmlConfigurator.Configure(); 來關聯配置文件。

          情況一: 使用默認配置文件的情況

          1. 代碼配置:log4net.Config.XmlConfigurator.Configure();

          2. 反射配置:[assembly: log4net.Config.XmlConfigurator()]

          (這里只是舉例,很少有修改默認配置文件名稱的)

          1. 代碼配置: 首先將App1.config文件的屬性中的“生成操作”改為“ 嵌入的資源”,然后通過以下代碼進行配置。

          1 Assembly assembly=Assembly.GetExecutingAssembly();
          2 var xml=assembly.GetManifestResourceStream("_01_SimpleDemo.App1.config");
          3 log4net.Config.XmlConfigurator.Configure(xml);
          

          注:代碼中的 _01_SimpleDemo 為命名空間名。

          2. 反射配置:[assembly: log4net.Config.XmlConfigurator(ConfigFile="log4net.xml")]

            注:用這種方式屬性中的:復制到輸出目錄需要改為:始終復制,生成操作不需要配置,使用默認:無 即可

          情況三:新建單獨xml文件,進行log4net的配置 (推薦采用這種方式,和原配置文件區分開,單獨配置方便,處理方式和情況二是一致的)

          1. 代碼配置:首先將log4net.xml文件的屬性中的“生成操作”改為“ 嵌入的資源”,然后通過以下代碼進行配置。

          1 Assembly assembly=Assembly.GetExecutingAssembly();
          2 var xml=assembly.GetManifestResourceStream("_01_SimpleDemo.log4net.xml");
          3 log4net.Config.XmlConfigurator.Configure(xml);
          

          注:代碼中的 _01_SimpleDemo 為命名空間名。

          情況四:無論是修改默認配置文件的名稱為 或者 新建單獨的xml作為配置文件 → 可以通過絕對路徑的方式進行處理 【不推薦這種方式】

          1. 直接寫絕對路徑 (注意這種方式【不需要】配置文件屬性為 “嵌入的資源”)

          1 log4net.Config.XmlConfigurator.Configure(new FileInfo(@"D:\06-我的開發之路\DotNet體系\05-DotNet框架篇\03-Log4net詳解\Code\01-SimpleDemo\log4net.xml"));
          

          2 通過代碼獲取絕對路徑 (注意這種方式【不需要】配置文件屬性的“生成操作”改為 “嵌入的資源”,但需要改為“始終復制”,確保輸出到bin文件下)

          1 string assemblyFilePath=Assembly.GetExecutingAssembly().Location;
          2 string assemblyDirPath=Path.GetDirectoryName(assemblyFilePath);
          3 string configFilePath=assemblyDirPath + " //log4net.xml";
          4 log4net.Config.XmlConfigurator.Configure(new FileInfo(configFilePath));
          

          PS:B/S程序下通過 log4net.Config.XmlConfigurator.Configure(new FileInfo(Server.MapPath("~") + @"/log4net.xml")); 來配置。

          四. 代碼調用詳解

          Log4net允許多個ILog對象同時存在,通過代碼:ILog log=LogManager.GetLogger("xxx"); 來創建。

          A: 日志級別由高到低分別為:FATAL(致命錯誤) > ERROR(一般錯誤) > WARN(警告) > INFO(一般信息) > DEBUG(調試信息),另外還有 OFF和 ALL 。

          幾點說明:OFF表示所有信息都不寫入,ALL表示所有信息都寫入,我們也可以通過:<root><level value="WARN" ></ level ></root>這樣配置,表示WARN級別以及高于WARN以上的級別才會被寫入日志

          B: 寫入日志的方法有:Debug、Error、Fatal、Info、Warn五個方法,每個方法都有兩個重載,如下圖:

          分享在使用配置文件為log4net.xml的情況下的調用代碼:

           1 Assembly assembly=Assembly.GetExecutingAssembly();
           2 var xml=assembly.GetManifestResourceStream("_01_SimpleDemo.log4net.xml");
           3 log4net.Config.XmlConfigurator.Configure(xml);
           4 ILog log=LogManager.GetLogger("test");
           5 log.Debug("調試信息");
           6 log.Info("一般信息");
           7 log.Warn("警告");
           8 try
           9 {
          10 int.Parse("ddd");
          11 }
          12 catch (Exception ex)
          13 {
          14 log.Error("一般錯誤", ex);
          15 }
          16 log.Fatal("致命錯誤");
          

          五. 配置文件詳解

          Log4net的配置文件主要分為兩大部分:分別是 【自定義配置節點】和 【核心代碼配置】,自定義配置節點代碼固定,如下圖,核心代碼配置主要位于:<log4net></log4net>節點中,里面包括<appender></appender>節點配置日日志輸出途徑 和 <root></root>節點,用于設置記錄日志的級別和啟用哪些輸出途徑。

          幾點說明:

          1. 自定義配置節點 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" /> 代碼固定,直接復制即可。

          2. <root></root> 節點主要用來: 配置日志的的輸出級別和加載日志的輸出途徑。

          A: level中的value值表示該值及其以上的日志級別才會輸出,日志級別包括:OFF > FATAL(致命錯誤) > ERROR(一般錯誤) > WARN(警告) > INFO(一般信息) > DEBUG(調試信息) > ALL ,比如:

              <level value="INFO"></level> 表示只有INFO及其以上的日志級別才會被保存。
          

          PS:OFF表示所有信息都不寫入,ALL表示所有信息都寫入。

          B: <appender-ref></appender-ref>標簽用于加載日志的輸出途徑代碼,通過ref和appender標簽的中name屬性相關聯,比如:

              <appender-ref ref="RollingFileAppender"></appender-ref> 表示開啟txt文檔保存日志的方式。
          

          3. <appender></appender>節點,用來配置日志的輸出途徑的,本節主要介紹了輸出到 【txt文本文檔】中 和 【數據庫】。

           A:分享一下數據庫的表結構,詳細配置見下面的代碼分享,需要注意字段類型相匹配,并且要顯式指定其長度。

          B:關于txt文本文檔的命名,可以存放到一個文件夾里,也可以按照時間來區分文件夾,并且命名可以 動態+指定命名的方式。

           C:關于日志文件大小的說明和文件個數的說明,主要需要三個節點配合使用(實際開發中,如果一個txt特別大,打開的時候會非常的慢,卡頓,所以該步驟有必要配置一下)。

          PS:首先要配置 RollingStyle 節點為Size模式或者Composite模式,然后配置 maximumFileSize 節點設置每個文件的大小,最后配置 MaxSizeRollBackups 節點,設置日志文件的個數。超出大小后在所有文件名后自動增加正整數重新命名,數字最大的最早寫入。

          用下面的代碼簡單測試一下:

          1 <param name="RollingStyle" value="Composite" />
          2 <param name="maximumFileSize" value="10KB" />
          3 <param name="MaxSizeRollBackups" value="5" />
            
          

           
          詳細代碼如下:
          
           1 <?xml version="1.0" encoding="utf-8" ?>
           2 <configuration>
           3 <!-- 一. 添加log4net的自定義配置節點-->
           4 <configSections>
           5 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
           6 </configSections>
           7 <!--二. log4net的核心配置代碼-->
           8 <log4net>
           9 <!--(一) 配置日志的輸出途徑-->
           10 <!--1. 輸出途徑(一) 將日志以回滾文件的形式寫到文件中-->
           11 <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
           12 <!--1.1 文件夾的位置(也可以寫相對路徑)-->
           13 <param name="File" value="D:\MyLog1\" />
           14 <!--相對路徑 C/S程序生成在Debug目錄下-->
           15 <!--<param name="File" value="/Logs/" />-->
           16 <!--1.2 是否追加到文件-->
           17 <param name="AppendToFile" value="true" />
           18 <!--1.3 使用最小鎖定模型(minimal locking model),以允許多個進程可以寫入同一個文件 -->
           19 <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
           20 <!--1.4 配置Unicode編碼-->
           21 <Encoding value="UTF-8" />
           22 <!--1.5 是否只寫到一個文件里-->
           23 <param name="StaticLogFileName" value="false" />
           24 <!--1.6 配置按照何種方式產生多個日志文件 (Date:日期、Size:文件大小、Composite:日期和文件大小的混合方式)-->
           25 <param name="RollingStyle" value="Composite" />
           26 <!--1.7 介紹多種日志的的命名和存放在磁盤的形式-->
           27 <!--1.7.1 在根目錄下直接以日期命名txt文件 注意"的位置,去空格 -->
           28 <param name="DatePattern" value="yyyy-MM-dd".log"" />
           29 <!--1.7.2 在根目錄下按日期產生文件夾,文件名固定 test.log -->
           30 <!--<param name="DatePattern" value="yyyy-MM-dd/"test.log"" />-->
           31 <!--1.7.3 在根目錄下按日期產生文件夾,這是按日期產生文件夾,并在文件名前也加上日期 -->
           32 <!--<param name="DatePattern" value="yyyyMMdd/yyyyMMdd"-test.log"" />-->
           33 <!--1.7.4 在根目錄下按日期產生文件夾,這再形成下一級固定的文件夾 -->
           34 <!--<param name="DatePattern" value="yyyyMMdd/"OrderInfor/test.log"" />-->
           35 <!--1.8 配置每個日志的大小。【只在1.6 RollingStyle 選擇混合方式與文件大小方式下才起作用!!!】可用的單位:KB|MB|GB。不要使用小數,否則會一直寫入當前日志,
           36 超出大小后在所有文件名后自動增加正整數重新命名,數字最大的最早寫入。-->
           37 <param name="maximumFileSize" value="10MB" />
           38 <!--1.9 最多產生的日志文件個數,超過則保留最新的n個 將value的值設置-1,則不限文件個數 【只在1.6 RollingStyle 選擇混合方式與文件大小方式下才起作用!!!】
           39 與1.8中maximumFileSize文件大小是配合使用的-->
           40 <param name="MaxSizeRollBackups" value="5" />
           41 <!--1.10 配置文件文件的布局格式,使用PatternLayout,自定義布局-->
           42 <layout type="log4net.Layout.PatternLayout">
           43 <conversionPattern value="記錄時間:%date %n線程ID:[%thread] %n日志級別:%-5level %n出錯類:%logger property: [%property{NDC}] - %n錯誤描述:%message%newline %n%newline"/>
           44 </layout>
           45 </appender>
           46 
           47 <!--2. 輸出途徑(二) 記錄日志到數據庫-->
           48 <appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
           49 <!--2.1 設置緩沖區大小,只有日志記錄超設定值才會一塊寫入到數據庫-->
           50 <param name="BufferSize" value="1" />
           51 <!--2.2 引用-->
           52 <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
           53 <!--2.3 數據庫連接字符串-->
           54 <connectionString value="data source=localhost;initial catalog=LogDB;integrated security=false;persist security info=True;User ID=sa;Password=123456" />
           55 <!--2.4 SQL語句插入到指定表-->
           56 <commandText value="INSERT INTO LogInfor ([threadId],[log_level],[log_name],[log_msg],[log_exception],[log_time]) VALUES (@threadId, @log_level, @log_name, @log_msg, @log_exception,@log_time)" />
           57 <!--2.5 數據庫字段匹配-->
           58 <!-- 線程號-->
           59 <parameter>
           60 <parameterName value="@threadId" />
           61 <dbType value="String" />
           62 <size value="100" />
           63 <layout type="log4net.Layout.PatternLayout">
           64 <conversionPattern value="%thread" />
           65 </layout>
           66 </parameter>
           67 <!--日志級別-->
           68 <parameter>
           69 <parameterName value="@log_level" />
           70 <dbType value="String" />
           71 <size value="100" />
           72 <layout type="log4net.Layout.PatternLayout">
           73 <conversionPattern value="%level" />
           74 </layout>
           75 </parameter>
           76 <!--日志記錄類名稱-->
           77 <parameter>
           78 <parameterName value="@log_name" />
           79 <dbType value="String" />
           80 <size value="100" />
           81 <layout type="log4net.Layout.PatternLayout">
           82 <conversionPattern value="%logger" />
           83 </layout>
           84 </parameter>
           85 <!--日志信息-->
           86 <parameter>
           87 <parameterName value="@log_msg" />
           88 <dbType value="String" />
           89 <size value="5000" />
           90 <layout type="log4net.Layout.PatternLayout">
           91 <conversionPattern value="%message" />
           92 </layout>
           93 </parameter>
           94 <!--異常信息 指的是如Infor 方法的第二個參數的值-->
           95 <parameter>
           96 <parameterName value="@log_exception" />
           97 <dbType value="String" />
           98 <size value="2000" />
           99 <layout type="log4net.Layout.ExceptionLayout" />
          100 </parameter>
          101 <!-- 日志記錄時間-->
          102 <parameter>
          103 <parameterName value="@log_time" />
          104 <dbType value="DateTime" />
          105 <layout type="log4net.Layout.RawTimeStampLayout" />
          106 </parameter>
          107 </appender>
          108 <!--(二). 配置日志的的輸出級別和加載日志的輸出途徑-->
          109 <root>
          110 <!--1. level中的value值表示該值及其以上的日志級別才會輸出-->
          111 <!--OFF > FATAL(致命錯誤) > ERROR(一般錯誤) > WARN(警告) > INFO(一般信息) > DEBUG(調試信息) > ALL -->
          112 <!--OFF表示所有信息都不寫入,ALL表示所有信息都寫入-->
          113 <level value="ALL"></level>
          114 <!--2. append-ref標簽表示要加載前面的日志輸出途徑代碼 通過ref和appender標簽的中name屬性相關聯-->
          115 <appender-ref ref="RollingFileAppender"></appender-ref>
          116 <appender-ref ref="AdoNetAppender"></appender-ref>
          117 </root>
          118 </log4net>
          119 
          120 </configuration>
          

          六. 簡單的封裝及完整代碼分享

          這里模擬在系統框架中對Log4net進行簡單的封裝,然后在MVC框架中調用,并分享全部代碼。

           步驟一:新建Ypf.Utils類庫,作為工具類庫,引入log4net程序集,并將前面用到的log4net.xml 復制進來,改屬性為嵌入資源,然后新建LogUtils類(不要起名為LogHelp),對Log4net的方法進行簡單的封裝,主要包括:初始化代碼、ILog實例創建、五類日志級別的封裝。

          特別注意:這種封裝會帶來一個問題,會導致輸出的文件中出錯類永遠顯示的為LogUtils這個封裝類,這里采用StackTrace類進行迂回處理一下,就可以定位到具體的出錯位置了,如下圖:

          log4net.xml文件代碼如下:

           1 <?xml version="1.0" encoding="utf-8" ?>
           2 <configuration>
           3 <!-- 一. 添加log4net的自定義配置節點-->
           4 <configSections>
           5 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
           6 </configSections>
           7 <!--二. log4net的核心配置代碼-->
           8 <log4net>
           9 <!--(一) 配置日志的輸出途徑-->
           10 <!--1. 輸出途徑(一) 將日志以回滾文件的形式寫到文件中-->
           11 <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
           12 <!--1.1 文件夾的位置(也可以寫相對路徑)-->
           13 <param name="File" value="D:\MyLog1\" />
           14 <!--相對路徑-->
           15 <!--<param name="File" value="Logs/" />-->
           16 <!--1.2 是否追加到文件-->
           17 <param name="AppendToFile" value="true" />
           18 <!--1.3 使用最小鎖定模型(minimal locking model),以允許多個進程可以寫入同一個文件 -->
           19 <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
           20 <!--1.4 配置Unicode編碼-->
           21 <Encoding value="UTF-8" />
           22 <!--1.5 是否只寫到一個文件里-->
           23 <param name="StaticLogFileName" value="false" />
           24 <!--1.6 配置按照何種方式產生多個日志文件 (Date:日期、Size:文件大小、Composite:日期和文件大小的混合方式)-->
           25 <param name="RollingStyle" value="Composite" />
           26 <!--1.7 介紹多種日志的的命名和存放在磁盤的形式-->
           27 <!--1.7.1 在根目錄下直接以日期命名txt文件 注意"的位置,去空格 -->
           28 <param name="DatePattern" value="yyyy-MM-dd".log"" />
           29 <!--1.7.2 在根目錄下按日期產生文件夾,文件名固定 test.log -->
           30 <!--<param name="DatePattern" value="yyyy-MM-dd/"test.log"" />-->
           31 <!--1.7.3 在根目錄下按日期產生文件夾,這是按日期產生文件夾,并在文件名前也加上日期 -->
           32 <!--<param name="DatePattern" value="yyyyMMdd/yyyyMMdd"-test.log"" />-->
           33 <!--1.7.4 在根目錄下按日期產生文件夾,這再形成下一級固定的文件夾 -->
           34 <!--<param name="DatePattern" value="yyyyMMdd/"OrderInfor/test.log"" />-->
           35 <!--1.8 配置每個日志的大小。【只在1.6 RollingStyle 選擇混合方式與文件大小方式下才起作用!!!】可用的單位:KB|MB|GB。不要使用小數,否則會一直寫入當前日志,
           36 超出大小后在所有文件名后自動增加正整數重新命名,數字最大的最早寫入。-->
           37 <param name="maximumFileSize" value="10MB" />
           38 <!--1.9 最多產生的日志文件個數,超過則保留最新的n個 將value的值設置-1,則不限文件個數 【只在1.6 RollingStyle 選擇混合方式與文件大小方式下才起作用!!!】
           39 與1.8中maximumFileSize文件大小是配合使用的-->
           40 <param name="MaxSizeRollBackups" value="5" />
           41 <!--1.10 配置文件文件的布局格式,使用PatternLayout,自定義布局-->
           42 <layout type="log4net.Layout.PatternLayout">
           43 <conversionPattern value="記錄時間:%date %n線程ID:[%thread] %n日志級別:%-5level %n出錯類:%logger property: [%property{NDC}] - %n錯誤描述:%message%newline %n%newline"/>
           44 </layout>
           45 </appender>
           46 
           47 <!--2. 輸出途徑(二) 記錄日志到數據庫-->
           48 <appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
           49 <!--2.1 設置緩沖區大小,只有日志記錄超設定值才會一塊寫入到數據庫-->
           50 <param name="BufferSize" value="1" />
           51 <!--2.2 引用-->
           52 <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
           53 <!--2.3 數據庫連接字符串-->
           54 <connectionString value="data source=localhost;initial catalog=LogDB;integrated security=false;persist security info=True;User ID=sa;Password=123456" />
           55 <!--2.4 SQL語句插入到指定表-->
           56 <commandText value="INSERT INTO LogInfor ([threadId],[log_level],[log_name],[log_msg],[log_exception],[log_time]) VALUES (@threadId, @log_level, @log_name, @log_msg, @log_exception,@log_time)" />
           57 <!--2.5 數據庫字段匹配-->
           58 <!-- 線程號-->
           59 <parameter>
           60 <parameterName value="@threadId" />
           61 <dbType value="String" />
           62 <size value="100" />
           63 <layout type="log4net.Layout.PatternLayout">
           64 <conversionPattern value="%thread" />
           65 </layout>
           66 </parameter>
           67 <!--日志級別-->
           68 <parameter>
           69 <parameterName value="@log_level" />
           70 <dbType value="String" />
           71 <size value="100" />
           72 <layout type="log4net.Layout.PatternLayout">
           73 <conversionPattern value="%level" />
           74 </layout>
           75 </parameter>
           76 <!--日志記錄類名稱-->
           77 <parameter>
           78 <parameterName value="@log_name" />
           79 <dbType value="String" />
           80 <size value="100" />
           81 <layout type="log4net.Layout.PatternLayout">
           82 <conversionPattern value="%logger" />
           83 </layout>
           84 </parameter>
           85 <!--日志信息-->
           86 <parameter>
           87 <parameterName value="@log_msg" />
           88 <dbType value="String" />
           89 <size value="5000" />
           90 <layout type="log4net.Layout.PatternLayout">
           91 <conversionPattern value="%message" />
           92 </layout>
           93 </parameter>
           94 <!--異常信息 指的是如Infor 方法的第二個參數的值-->
           95 <parameter>
           96 <parameterName value="@log_exception" />
           97 <dbType value="String" />
           98 <size value="2000" />
           99 <layout type="log4net.Layout.ExceptionLayout" />
          100 </parameter>
          101 <!-- 日志記錄時間-->
          102 <parameter>
          103 <parameterName value="@log_time" />
          104 <dbType value="DateTime" />
          105 <layout type="log4net.Layout.RawTimeStampLayout" />
          106 </parameter>
          107 </appender>
          108 <!--(二). 配置日志的的輸出級別和加載日志的輸出途徑-->
          109 <root>
          110 <!--1. level中的value值表示該值及其以上的日志級別才會輸出-->
          111 <!--OFF > FATAL(致命錯誤) > ERROR(一般錯誤) > WARN(警告) > INFO(一般信息) > DEBUG(調試信息) > ALL -->
          112 <!--OFF表示所有信息都不寫入,ALL表示所有信息都寫入-->
          113 <level value="ALL"></level>
          114 <!--2. append-ref標簽表示要加載前面的日志輸出途徑代碼 通過ref和appender標簽的中name屬性相關聯-->
          115 <appender-ref ref="RollingFileAppender"></appender-ref>
          116 <appender-ref ref="AdoNetAppender"></appender-ref>
          117 </root>
          118 </log4net>
          119 
          120 </configuration>
          

          LogUtils類代碼如下

           1 using log4net;
           2 using System;
           3 using System.Collections.Generic;
           4 using System.Diagnostics;
           5 using System.Linq;
           6 using System.Reflection;
           7 using System.Text;
           8 using System.Threading.Tasks;
           9 
           10 namespace Ypf.Utils
           11 {
           12 public class LogUtils
           13 {
           14 //可以聲明多個日志對象
           15 public static ILog log=LogManager.GetLogger(typeof(LogUtils));
           16 
           17 #region 01-初始化Log4net的配置
           18 /// <summary>
           19 /// 初始化Log4net的配置
           20 /// xml文件一定要改為嵌入的資源
           21 /// </summary>
           22 public static void InitLog4Net()
           23 {
           24 Assembly assembly=Assembly.GetExecutingAssembly();
           25 var xml=assembly.GetManifestResourceStream("Ypf.Utils.log4net.xml");
           26 log4net.Config.XmlConfigurator.Configure(xml);
           27 }
           28 #endregion
           29 
           30 
           31 
           32 
           33 /************************* 五種不同日志級別 *******************************/
           34 //FATAL(致命錯誤) > ERROR(一般錯誤) > WARN(警告) > INFO(一般信息) > DEBUG(調試信息)
           35 
           36 /// <summary>
           37 /// 將調試的信息輸出,可以定位到具體的位置(解決高層封裝帶來的問題)
           38 /// </summary>
           39 /// <returns></returns>
           40 private static string getDebugInfo()
           41 {
           42 StackTrace trace=new StackTrace(true);
           43 return trace.ToString();
           44 }
           45 
           46 #region 01-DEBUG(調試信息)
           47 /// <summary>
           48 /// Debug
           49 /// </summary>
           50 /// <param name="msg">日志信息</param>
           51 public static void Debug(string msg)
           52 {
           53 log.Debug(getDebugInfo() + msg);
           54 }
           55 /// <summary>
           56 /// Debug
           57 /// </summary>
           58 /// <param name="msg">日志信息</param>
           59 /// <param name="exception">錯誤信息</param>
           60 public static void Debug(string msg, Exception exception)
           61 {
           62 log.Debug(getDebugInfo() + msg, exception);
           63 }
           64 
           65 #endregion
           66 
           67 #region 02-INFO(一般信息)
           68 /// <summary>
           69 /// Info
           70 /// </summary>
           71 /// <param name="msg">日志信息</param>
           72 public static void Info(string msg)
           73 {
           74 log.Info(getDebugInfo() + msg);
           75 }
           76 /// <summary>
           77 /// Info
           78 /// </summary>
           79 /// <param name="msg">日志信息</param>
           80 /// <param name="exception">錯誤信息</param>
           81 public static void Info(string msg, Exception exception)
           82 {
           83 log.Info(getDebugInfo() + msg, exception);
           84 }
           85 #endregion
           86 
           87 #region 03-WARN(警告)
           88 /// <summary>
           89 /// Warn
           90 /// </summary>
           91 /// <param name="msg">日志信息</param>
           92 public static void Warn(string msg)
           93 {
           94 log.Warn(getDebugInfo() + msg);
           95 }
           96 /// <summary>
           97 /// Warn
           98 /// </summary>
           99 /// <param name="msg">日志信息</param>
          100 /// <param name="exception">錯誤信息</param>
          101 public static void Warn(string msg, Exception exception)
          102 {
          103 log.Warn(getDebugInfo() + msg, exception);
          104 }
          105 #endregion
          106 
          107 #region 04-ERROR(一般錯誤)
          108 /// <summary>
          109 /// Error
          110 /// </summary>
          111 /// <param name="msg">日志信息</param>
          112 public static void Error(string msg)
          113 {
          114 log.Error(getDebugInfo() + msg);
          115 }
          116 /// <summary>
          117 /// Error
          118 /// </summary>
          119 /// <param name="msg">日志信息</param>
          120 /// <param name="exception">錯誤信息</param>
          121 public static void Error(string msg, Exception exception)
          122 {
          123 log.Error(getDebugInfo() + msg, exception);
          124 }
          125 #endregion
          126 
          127 #region 05-FATAL(致命錯誤)
          128 /// <summary>
          129 /// Fatal
          130 /// </summary>
          131 /// <param name="msg">日志信息</param>
          132 public static void Fatal(string msg)
          133 {
          134 log.Fatal(getDebugInfo() + msg);
          135 }
          136 /// <summary>
          137 /// Fatal
          138 /// </summary>
          139 /// <param name="msg">日志信息</param>
          140 /// <param name="exception">錯誤信息</param>
          141 public static void Fatal(string msg, Exception exception)
          142 {
          143 log.Fatal(getDebugInfo() + msg, exception);
          144 }
          145 
          146 #endregion
          147 
          148 
          149 
          150 }
          151 }
          

          步驟二:新建名Ypf.MVC的MVC5框架,添加對Ypf.Utils類庫的引用,在Global.asax全局文件中添加對 對Log4net進行初始化。

          然后就可以愉快的進行調用測試了哦。

           1 /// <summary>
           2 /// 測試log4net
           3 /// 首先需要再Global中初始化log4net
           4 /// </summary>
           5 /// <returns></returns>
           6 public ActionResult Index()
           7 {
           8 //FATAL(致命錯誤) > ERROR(一般錯誤) > WARN(警告) > INFO(一般信息) > DEBUG(調試信息)
           9 LogUtils.Debug("出錯了");
          10 try
          11 {
          12 int.Parse("ddf");
          13 }
          14 catch (Exception ex)
          15 {
          16 LogUtils.Debug("出錯了",ex);
          17 }
          18 
          19 LogUtils.Info("出錯了");
          20 try
          21 {
          22 int.Parse("ddf");
          23 }
          24 catch (Exception ex)
          25 {
          26 LogUtils.Info("出錯了", ex);
          27 }
          28 
          29 LogUtils.Warn("出錯了");
          30 try
          31 {
          32 int.Parse("ddf");
          33 }
          34 catch (Exception ex)
          35 {
          36 LogUtils.Warn("出錯了", ex);
          37 }
          38 
          39 LogUtils.Error("出錯了");
          40 try
          41 {
          42 int.Parse("ddf");
          43 }
          44 catch (Exception ex)
          45 {
          46 LogUtils.Error("出錯了", ex);
          47 }
          48 
          49 LogUtils.Fatal("出錯了");
          50 try
          51 {
          52 int.Parse("ddf");
          53 }
          54 catch (Exception ex)
          55 {
          56 LogUtils.Fatal("出錯了", ex);
          57 }
          58 
          59 return View();
          60 }
          

          七. 補充:分文件存放

          在前面的介紹中,忽略了一種情況,各種類型的日志(操作日志也好,錯誤日志也好)都存放在一個txt文檔里,在實際開發中很不方便,在這里介紹一種利用Log4net過濾器實現不同日志分文件夾存放的功能。

          幾種過濾器,可以用來過濾掉Appender中的內容:

          DenyAllFilter: 阻止所有的日志事件被記錄

          LevelMatchFilter: 只有指定等級的日志事件才被記錄

          LevelRangeFilter :日志等級在指定范圍內的事件才被記錄

          LoggerMatchFilter: 與Logger名稱匹配,才記錄

          PropertyFilter: 消息匹配指定的屬性值時才被記錄

          StringMathFilter: 消息匹配指定的字符串才被記錄

          分文件夾存放的核心所在:

          ①. 配置文件的調整:利用LoggerMatchFilter和DenyAllFilter過濾器實現分文件存放。

          ②:聲明ILog對象的時候,需要與LoggerMatchFilter過濾器中的Value值相對應,才能保證該ILog對象記錄的日志存放到該Appender節點對應的路徑下。

          ③. 高層調用:

          分享一下log4net的配置文件和LogUtils的封裝文件。


          主站蜘蛛池模板: 亚洲综合色一区二区三区| 婷婷亚洲综合一区二区| 欧美日韩精品一区二区在线观看| 一区二区福利视频| 国产精品一区二区久久精品| 久久免费国产精品一区二区| 精品国产AV无码一区二区三区| 91大神在线精品视频一区| 手机看片福利一区二区三区| 日本精品一区二区三区四区| 无码人妻久久一区二区三区免费丨 | 国产精品一区二区久久国产| 国产一区二区三区在线| 精品国产一区二区三区久 | 亚洲国产精品综合一区在线 | 国产AV一区二区精品凹凸| 亚洲欧美日韩中文字幕在线一区| 夜夜添无码一区二区三区| 日本人真淫视频一区二区三区| 国产经典一区二区三区蜜芽| 一区二区三区视频在线播放| 国模无码一区二区三区不卡| 国模少妇一区二区三区| 亚洲高清日韩精品第一区| 波多野结衣一区在线| 亚洲一区影音先锋色资源| 久久久无码精品国产一区| 一区二区中文字幕| 亚洲高清美女一区二区三区 | 国产精品一区二区综合| 日韩亚洲一区二区三区| 国产成人av一区二区三区不卡 | 国产成人精品无人区一区| 无码国产伦一区二区三区视频 | 亚洲AV色香蕉一区二区| 国产福利一区二区三区在线视频 | 国产精品综合AV一区二区国产馆| 国产精品无码一区二区在线 | 亚洲AV成人精品日韩一区 | 成人免费一区二区三区在线观看| 夜色阁亚洲一区二区三区|