Asp.NET MVC, пользовательский TextWriterTraceListener не создает файл

Для приложения MVC пользовательский прослушиватель не создает файл журнала, когда используется параметр initializeData="CustomWeblog.txt", но initializeData="d:\CustomWeblog.txt" запускает создание файла. В чем причина такого поведения? Консольное приложение генерирует файлы для всех типов слушателей.

Пользовательский класс:

public class CustomTextWriterTraceListener : TextWriterTraceListener 
{ 
     public CustomTextWriterTraceListener(string fileName) : base(fileName)
}

Web.config (приложение mvc, web.config)

<trace autoflush="true" />
<sources>
  <source name="Trace">    
      <listeners>
        <add name="TextWriterListner"
             type="System.Diagnostics.TextWriterTraceListener, WebTracing" initializeData="Weblog.txt"/>
        <!-- the file is created -->
        <add name="CustomTextWriterListner"
             type="WebTracing.CustomTextWriterTraceListener, WebTracing" initializeData="CustomWeblog.txt"/>
        <!-- the file is not created in MVC application ?! -->
        <add name="CustomTextWriterListnerAbsolutePath"
             type="WebTracing.CustomTextWriterTraceListener, WebTracing" initializeData="d:\CustomWeblog.txt"/>
        <!-- the file is created -->
      </listeners>      
  </source>
</sources>

Слушатель Cutom не создает файл журнала.

Абонент:

TraceSource obj = new TraceSource("Trace", SourceLevels.All);
obj.TraceEvent(TraceEventType.Critical,0,"This is a critical message");

Я попытался добавить некоторые дополнительные настройки: из этого блога и этого. Но успеха нет. Должен ли я предоставить абсолютный путь? Есть ли обходной путь, создав отдельную сборку для пользовательского слушателя?

2 ответа

Решение

Я пытался создать свой собственный слушатель трассировки автора текста, когда столкнулся с той же проблемой, которую вы описали. Короче говоря, после всего, что я здесь придумала, это то, что я придумал.

public class RollingTextWriterTraceListener : TextWriterTraceListener {
    string fileName;
    private static string[] _supportedAttributes = new string[] 
        { 
            "template", "Template", 
            "convertWriteToEvent", "ConvertWriteToEvent",
            "addtoarchive","addToArchive","AddToArchive",
        };

    public RollingTextWriterTraceListener(string fileName)
        : base() {
        this.fileName = fileName;
    }
    /// <summary>
    /// This makes sure that the writer exists to be written to.
    /// </summary>
    private void ensureWriter() {
        //Resolve file name given. relative paths (if present) are resolved to full paths.
        // Also allows for paths like this: initializeData="~/Logs/{ApplicationName}_{DateTime:yyyy-MM-dd}.log"
        var logFileFullPath = ServerPathUtility.ResolvePhysicalPath(fileName);
        var writer = base.Writer;
        if (writer == null && createWriter(logFileFullPath)) {
            writer = base.Writer;
        }
        if (!File.Exists(logFileFullPath)) {
            if (writer != null) {
                try {
                    writer.Flush();
                    writer.Close();
                    writer.Dispose();
                } catch (ObjectDisposedException) { }
            }
            createWriter(logFileFullPath);
        }
        //Custom code to package the previous log file(s) into a zip file.
        if (AddToArchive) {
            TextFileArchiveHelper.Archive(logFileFullPath);
        }
    }

    bool createWriter(string logFileFullPath) {
        try {
            logFileFullPath = ServerPathUtility.ResolveOrCreatePath(logFileFullPath);
            var writer = new StreamWriter(logFileFullPath, true);
            base.Writer = writer;
            return true;
        } catch (IOException) {
            //locked as already in use
            return false;
        } catch (UnauthorizedAccessException) {
            //ERROR_ACCESS_DENIED, mostly ACL issues
            return false;
        }
    }

    /// <summary>
    /// Get the add to archive flag
    /// </summary>
    public bool AddToArchive {
        get {
            // Default behaviour is not to add to archive.
            var addToArchive = false;
            var key = Attributes.Keys.Cast<string>().
                FirstOrDefault(s => string.Equals(s, "addtoarchive", StringComparison.InvariantCultureIgnoreCase));
            if (!string.IsNullOrWhiteSpace(key)) {
                bool.TryParse(Attributes[key], out addToArchive);
            }
            return addToArchive;
        }
    }

    #region Overrides
    /// <summary>
    /// Allowed attributes for this trace listener.
    /// </summary>
    protected override string[] GetSupportedAttributes() {
        return _supportedAttributes;
    }

    public override void Flush() {
        ensureWriter();
        base.Flush();
    }

    public override void Write(string message) {
        ensureWriter();
        base.Write(message);
    }

    public override void WriteLine(string message) {
        ensureWriter();
        base.WriteLine(message);
    }
    #endregion
}

ОБНОВЛЕНИЕ: Вот служебный класс, который я написал для разрешения путей.

public static class ServerPathUtility {

    public static string ResolveOrCreatePath(string pathToReplace) {
        string rootedFileName = ResolvePhysicalPath(pathToReplace);
        FileInfo fi = new FileInfo(rootedFileName);
        try {
            DirectoryInfo di = new DirectoryInfo(fi.DirectoryName);
            if (!di.Exists) {
                di.Create();
            }
            if (!fi.Exists) {
                fi.CreateText().Close();
            }
        } catch {
            // NO-OP
            // TODO: Review what should be done here.
        }
        return fi.FullName;
    }

    public static string ResolvePhysicalPath(string pathToReplace) {
        string rootedPath = ResolveFormat(pathToReplace);
        if (rootedPath.StartsWith("~") || rootedPath.StartsWith("/")) {
            rootedPath = System.Web.Hosting.HostingEnvironment.MapPath(rootedPath);
        } else if (!Path.IsPathRooted(rootedPath)) {
            rootedPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, rootedPath);
        }
        return rootedPath;
    }

    public static string ResolveFormat(string format) {
        string result = format;

        try {
            result = ExpandApplicationVariables(format);
        } catch (System.Security.SecurityException) {
            // Log?
        }

        try {
            string variables = Environment.ExpandEnvironmentVariables(result);
            // If an Environment Variable is not found then remove any invalid tokens
            Regex filter = new Regex("%(.*?)%", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);

            string filePath = filter.Replace(variables, "");

            if (Path.GetDirectoryName(filePath) == null) {
                filePath = Path.GetFileName(filePath);
            }
            result = filePath;
        } catch (System.Security.SecurityException) {
            // Log?
        }

        return result;
    }

    public static string ExpandApplicationVariables(string input) {
        var filter = new Regex("{(.*?)}", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
        var result = filter.Replace(input, evaluateMatch());
        return result;
    }

    private static MatchEvaluator evaluateMatch() {
        return match => {
            var variableName = match.Value;
            var value = GetApplicationVariable(variableName);
            return value;
        };
    }

    public static string GetApplicationVariable(string variable) {
        string value = string.Empty;
        variable = variable.Replace("{", "").Replace("}", "");
        var parts = variable.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
        variable = parts[0];
        var parameter = string.Empty;
        if (parts.Length > 1) {
            parameter = string.Join("", parts.Skip(1));
        }

        Func<string, string> resolve = null;
        value = VariableResolutionStrategies.TryGetValue(variable.ToUpperInvariant(), out resolve) && resolve != null
            ? resolve(parameter)
            : string.Empty;

        return value;
    }

    public static readonly IDictionary<string, Func<string, string>> VariableResolutionStrategies =
        new Dictionary<string, Func<string, string>> {
            {"MACHINENAME", p => Environment.MachineName },
            {"APPDOMAIN", p => AppDomain.CurrentDomain.FriendlyName },
            {"DATETIME", getDate},
            {"DATE", getDate},
            {"UTCDATETIME", getUtcDate},
            {"UTCDATE", getUtcDate},
        };

    static string getDate(string format = "yyyy-MM-dd") {
        var value = string.Empty;
        if (string.IsNullOrWhiteSpace(format))
            format = "yyyy-MM-dd";
        value = DateTime.Now.ToString(format);
        return value;
    }

    static string getUtcDate(string format = "yyyy-MM-dd") {
        var value = string.Empty;
        if (string.IsNullOrWhiteSpace(format))
            format = "yyyy-MM-dd";
        value = DateTime.Now.ToString(format);
        return value;
    }
}

Так что этот служебный класс позволяет мне разрешать относительные пути, а также настраивать форматы. Например, если вы посмотрите на код, вы увидите это имя приложения ApplicationName переменная не существует в этом пути

"~/Logs/{ApplicationName}_{DateTime:yyyy-MM-dd}.log"

Я могу настроить это при запуске приложения вместе с любыми другими переменными, которые я хочу добавить, вот так

public partial class Startup {
    public void Configuration(IAppBuilder app) {
        //... Code removed for brevity           
        // Add APPLICATIONNAME name to path Utility
        ServerPathUtility.VariableResolutionStrategies["APPLICATIONNAME"] = p => {
            var assembly = System.Reflection.Assembly.GetExecutingAssembly();
            if (assembly != null)
                return assembly.GetName().Name;
            return string.Empty;
        };           
    }
}

Хорошо, наконец, я переключил исследование на способ генерации путей слушателя. Что я заметил при отладке, так это то, что список слушателей источника содержит разные пути.

  • Объект слушателя System.Diagnostics.TextWriterTraceListener имеет полный сгенерированный путь;
  • WebTracing.CustomTextWriterTraceListener имеет только имя файла. Там нет сгенерированных ошибок.

Различные значения, вызванные причиной, по которой пользовательский прослушиватель поглотил исключение UnauthorisedAccessException, так что приложение продолжает работать, не сообщая нам о проблемах с разрешениями.

Но каково место хранения пользовательских файлов журнала прослушивателя? Они созданы или нет?

Следующая ссылка на исходный код TextWriterTraceListener помогла мне выяснить путь. Следующий код:

//initializeData="CustomWeblog.txt", so fileName == "CustomWeblog.txt" here
string fullPath = Path.GetFullPath(fileName); 
string dirPath = Path.GetDirectoryName(fullPath); 
string fileNameOnly = Path.GetFileName(fullPath);

Фактический путь к хранилищу зависит от проекта> Свойства> Интернет> Сервер: IIS Express:

c: \ Program Files (x86) \ IIS Express \ CustomWeblog.txt

Все время, пока я создавал приложение MVC (от имени администратора: от имени администратора), в этой папке правильно создавались файлы журнала. Когда я запускаю VS без прав администратора, пользовательские слушатели не создают файлы вообще.

Как было указано выше, я выполнил прослушиватель исходного кода и обнаружил, что catch(UnauthorisedAccessException) { break; } срабатывает на new StreamWriter(...) вызов конструктора.


В качестве другого обходного пути вы можете объявить весь путь в initializeData="d:\CustomWeblog.txt" приписывать. Но имейте в виду, что у вас должны быть соответствующие разрешения.

Другие вопросы по тегам