ほのぼの C#開発

開発の基礎から、現場で使用できるC#を掲載していきます。

C# log4net ログの世代管理

log4net ログの世代管理

log4net ログの世代管理について紹介します。

 今回は、ある一定期間を過ぎたログファイルを削除する。

ログの削除

//-------------------------------------------------
// ログの削除(世代管理)
//-------------------------------------------------
new LogFileCleanupTask().CleanUp();

 

ログの世代管理クラスについて

ログの保持すべき最終の日付を取得し、過去のものは削除する。

using System;
using System.IO;
using System.Linq;
using log4net;
using log4net.Appender;

namespace Common
{

    /// <summary>
    /// ログ世代管理のタスククラスです。
    /// </summary>
    public class LogFileCleanupTask
    {
        /// <summary>
        /// ログの世代管理の削除を行います。
        /// </summary>
        public void CleanUp()
        {
            var repo = LogManager.GetAllRepositories().FirstOrDefault();

            if (repo == null)
            {
                throw new NotSupportedException("Log4Netが設定されていません");
            }

            var app = repo.GetAppenders().FirstOrDefault(x => x.GetType() == typeof (RollingFileAppender));

            if (app != null)
            {
                var appender = app as RollingFileAppender;
                
                var directory = Path.GetDirectoryName(appender.File);
                var filePrefix = Path.GetFileName(appender.File);
                filePrefix = filePrefix.Substring(0, filePrefix.Length - appender.DatePattern.Replace("\"", "").Length);

        // ログの最終保持日付
                var targetDate = DateTime.Now.Date.AddDays(-appender.MaxSizeRollBackups);
                
                // 削除の実行
                CleanUp(directory, filePrefix,targetDate, appender);
            }
        }

 

 最終日付より過去のログファイルを削除

        /// <summary>
        /// ログの世代管理の削除の実行
        /// </summary>
        /// <param name="logDirectory">削除対象のディレクトリ.</param>
        /// <param name="logPrefix">ファイルのフォーマット名</param>
        /// <param name="lastDate">世代管理の開始日</param>
        /// <param name="appender">RollingFileAppender</param>
        private void CleanUp(string logDirectory, string logPrefix,DateTime lastDate, RollingFileAppender appender)
        {
            if (string.IsNullOrEmpty(logDirectory))
            {
                throw new ArgumentException("ログのディレクトリが存在しません");
            }

            if (string.IsNullOrEmpty(logDirectory))
            {
                throw new ArgumentException("ログのファイルフォーマット名の形式が存在しません");
            }

            var dirInfo = new DirectoryInfo(logDirectory);
            if (!dirInfo.Exists)
            {
                return;
            }

            // 削除
            dirInfo.GetFiles(String.Format("{0}*.*", logPrefix))
                .Where(info => GetLogDate(info, logPrefix, appender) < lastDate)
                .ForEach(info =>
                {
                    if ((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                        info.Attributes = FileAttributes.Normal;

                    Loggers.Log.Debug(string.Format("世代管理に伴い、過去のログファイルの削除を行います。ファイル:{0}",info.FullName));
                    // 削除
                    info.Delete();
                });

        }

        /// <summary>
        /// ログの作成日付を取得します。
        /// </summary>
        /// <param name="fileInfo">ファイル情報オブジェクト</param>
        /// <param name="logPrefix">ログファイル名のフォーマット</param>
        /// <param name="app">ログのアペンダー</param>
        /// <returns>ログの作成日付</returns>
        private DateTime GetLogDate(FileInfo fileInfo, string logPrefix,RollingFileAppender app)
        {
            var year = fileInfo.Name.Substring(logPrefix.Length + app.DatePattern.IndexOf("yyyy"), 4);
            var month = fileInfo.Name.Substring(logPrefix.Length + app.DatePattern.IndexOf("MM"), 2);
            var day = fileInfo.Name.Substring(logPrefix.Length + app.DatePattern.IndexOf("dd"), 2);

            return new DateTime(Convert.ToInt32(year), Convert.ToInt32(month), Convert.ToInt32(day));
        }
    }
}

 

C# パフォーマンスの向上

パフォーマンスの問題

現場にいると

・画面表示される速度が遅い
・初回表示するのが遅い
・使っているとだんだん遅くなる

など色々な意見をもらう。

 

⇒まず、どこで遅いか切り分ける。

 クライアント描画処理?

 ネットワークの負荷率?

 サーバー処理?

 DB処理?

 クライアント描画処理

  

  ・指定した箇所で描画が行われているか?

  ・同じ処理が何度も実行されていないかなか?

   ⇒デバッグで調査

   

ネットワークの負荷率?

  WEB系だとレスポンスをブラウザ(Chrome)等のツールで把握可能

  Fiddlerを使用するとどこで遅いか一目瞭然

  Fiddler - Free Web Debugging Proxy - Telerik

 

  Fiddlerで各通信の速度が表示される(ネットワークのトラフィックに問題か確認可能)

  IEIIS

  IISASP.Net

  ※ただし、WEBサーバー⇒DBサーバの通信の問題は分からない。

  改善方法

   インフラ周りの改善(お金がかかる)

   引数・戻り値のデータ圧縮

   通信の際に最小限のデータのやりとりとする(制約やデータの見直し)

  

 サーバ処理

  VisualStudioのプロファイラを使用して、デバッグする。

  CPUの負荷がかかっている箇所が分かる。 

  https://msdn.microsoft.com/ja-jp/library/ms182372.aspx

 

 DB処理

  SQLサーバーで直接指定のSQLを実行する

  ※初回だけ遅い等の症状がある場合は、SQLサーバーのキャッシュを

  削除してから行う。

DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE

 

SQLが遅い場合は、

  ・インデックスの見直し

  ・ストアドにするか検討

  ・バッチで集計するかとう検討

 

初回のみ症状が発生した場合

  初回ストアド等の読み込み等に時間がかかるため、調整が必要。

   

問題が発生しなかった場合

  APサーバからDBサーバの通信問題のため、

   ・インフラ対応

   ・取得するデータ量の見直し・設計見直し

 

  ※SQL回数分遅くなるため、下記のような対応も必要な可能性あり。

       BulkCopy

                         SQL Server でのバルク コピー操作 | Microsoft Docs

       APサーバのキャッシュを保持

C# リトライ処理

 リトライ処理

リトライする処理とエラー処理を設定するのみ。

 

責務を分けることが可能となる。

リトライの共通処理:RetryExecutor
リトライ処理   :内部処理

 

リトライ処理の呼び出し方

//------------------------------------------
// リトライ用の実行クラスの生成
//------------------------------------------
_executor = new RetryExecutor(new RetryPolicy()
{
    MaxRetryNum = CommonData.Instance.RetryCount,
    RetrySleep = CommonData.Instance.RetryWaitSecond * 1000
});


//------------------------------------------
// リトライ処理実行
//------------------------------------------
var result = _executor.Execute(() =>
{
    //  繰り返し実行したい処理を記載

    return returnCode; 
}, ex =>
{
    // エラー処理

}); 

 

リトライ処理用の共通クラス

プロパティ

 ・最大リトライ回数

 ・リトライ時の待ち時間

関数

 ・リトライ実行処理(Simple) 

  ・リトライ実行処理(エラー時の対処方法)

using System;
using System.Threading;

namespace Common
{
    /// <summary>
    /// リトライ処理が必要な場合の、リトライのルールを定義する抽象クラスです。
    /// </summary>
    public class RetryPolicy
    {
        /// <summary>
        /// 最大リトライ回数
        /// </summary>
        public int MaxRetryNum { get; set; }

        /// <summary>
        /// リトライ時のベースとなる処理停止時間(ミリ秒)
        /// </summary>
        public int RetrySleep { get; set; }
    }

    /// <summary>
    /// リトライ処理が必要な箇所で指定された条件によってリトライを行うためのクラスです。
    /// </summary>
    public class RetryExecutor
    {
        private readonly RetryPolicy _policy;

        /// <summary>
        /// リトライルールを受け取ってインスタンスを生成します。
        /// </summary>
        /// <param name="policy"></param>
        public RetryExecutor(RetryPolicy policy)
        {
            _policy = policy;
        }

        /// <summary>
        /// リトライをかけながら指定された処理を実行します。
        /// </summary>
        /// <param name="executeAction">実行する処理</param>
        public virtual void Execute(Action executeAction)
        {
            for (var retryNum = 1; retryNum <= _policy.MaxRetryNum + 1; retryNum++)
            {
                try
                {
                    executeAction();
                    break;
                }
                catch (Exception ex)
                {
                    Loggers.Log.Error(ex.ToString());

                    if (retryNum > _policy.MaxRetryNum)
                    {
                        throw new Exception(string.Format("リトライ回数をオーバーしたため、処理を終了します。最大リトライ回数:{0}", (_policy.MaxRetryNum).ToString()));
                    }

                    Loggers.Log.Warn(string.Format("一定時間[{0}ミリ秒]待機した後、リトライします。リトライ回数:{1}", _policy.RetrySleep.ToString(), retryNum.ToString()));
                    Thread.Sleep(_policy.RetrySleep);
                }
            }
        }

        /// <summary>
        /// リトライをかけながら指定された処理を実行します。
        /// </summary>
        /// <param name="executeAction">実行する処理</param>
        /// <param name="errorAction">処理実行時に発生したエラー処理</param>
        public virtual T Execute<T>(Func<T> executeAction, Action<Exception> errorAction = null)
        {
            var result = default(T);
            for (var retryNum = 1; retryNum <= _policy.MaxRetryNum + 1; retryNum++)
            {
                try
                {
                    result = executeAction();
                    break;
                }
                catch (Exception ex)
                {
                    Loggers.Log.Error(ex.ToString());

                    if (errorAction != null)
                    {
                        errorAction(ex);
                    }

                    if (retryNum > _policy.MaxRetryNum)
                    {
                        throw new Exception(string.Format("リトライ回数をオーバーしたため、処理を終了します。最大リトライ回数:{0}", (_policy.MaxRetryNum).ToString()));
                    }

                    Loggers.Log.Warn(string.Format("一定時間[{0}ミリ秒]待機した後、リトライします。リトライ回数:{1}",_policy.RetrySleep.ToString(), retryNum.ToString()));
                    Thread.Sleep(_policy.RetrySleep);
                }
            }

            return result;
        }
    }
}