using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Serilog.Events; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Logging.Viewer { public abstract class LogViewerSourceBase : ILogViewer { protected LogViewerSourceBase(string pathToSearches = "") { if (string.IsNullOrEmpty(pathToSearches)) // ReSharper disable once StringLiteralTypo pathToSearches = IOHelper.MapPath("~/Config/logviewer.searches.config.js"); _searchesConfigPath = pathToSearches; } private readonly string _searchesConfigPath; public abstract bool CanHandleLargeLogs { get; } /// /// Get all logs from your chosen data source back as Serilog LogEvents /// protected abstract IReadOnlyList GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take); public abstract bool CheckCanOpenLogs(LogTimePeriod logTimePeriod); public virtual IReadOnlyList GetSavedSearches() { //Our default implementation //If file does not exist - lets create it with an empty array EnsureFileExists(_searchesConfigPath, "[]"); var rawJson = System.IO.File.ReadAllText(_searchesConfigPath); return JsonConvert.DeserializeObject(rawJson); } public virtual IReadOnlyList AddSavedSearch(string name, string query) { //Get the existing items var searches = GetSavedSearches().ToList(); //Add the new item to the bottom of the list searches.Add(new SavedLogSearch { Name = name, Query = query }); //Serialize to JSON string var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented); //If file does not exist - lets create it with an empty array EnsureFileExists(_searchesConfigPath, "[]"); //Write it back down to file System.IO.File.WriteAllText(_searchesConfigPath, rawJson); //Return the updated object - so we can instantly reset the entire array from the API response //As opposed to push a new item into the array return searches; } public virtual IReadOnlyList DeleteSavedSearch(string name, string query) { //Get the existing items var searches = GetSavedSearches().ToList(); //Removes the search searches.RemoveAll(s => s.Name.Equals(name) && s.Query.Equals(query)); //Serialize to JSON string var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented); //Write it back down to file System.IO.File.WriteAllText(_searchesConfigPath, rawJson); //Return the updated object - so we can instantly reset the entire array from the API response return searches; } public int GetNumberOfErrors(LogTimePeriod logTimePeriod) { var errorCounter = new ErrorCounterFilter(); GetLogs(logTimePeriod, errorCounter, 0, int.MaxValue); return errorCounter.Count; } public LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod) { var counter = new CountingFilter(); GetLogs(logTimePeriod, counter, 0, int.MaxValue); return counter.Counts; } public IEnumerable GetMessageTemplates(LogTimePeriod logTimePeriod) { var messageTemplates = new MessageTemplateFilter(); GetLogs(logTimePeriod, messageTemplates, 0, int.MaxValue); var templates = messageTemplates.Counts. Select(x => new LogTemplate { MessageTemplate = x.Key, Count = x.Value }) .OrderByDescending(x=> x.Count); return templates; } public PagedResult GetLogs(LogTimePeriod logTimePeriod, int pageNumber = 1, int pageSize = 100, Direction orderDirection = Direction.Descending, string filterExpression = null, string[] logLevels = null) { var expression = new ExpressionFilter(filterExpression); var filteredLogs = GetLogs(logTimePeriod, expression, 0, int.MaxValue); //This is user used the checkbox UI to toggle which log levels they wish to see //If an empty array or null - its implied all levels to be viewed if (logLevels?.Length > 0) { var logsAfterLevelFilters = new List(); var validLogType = true; foreach (var level in logLevels) { //Check if level string is part of the LogEventLevel enum if(Enum.IsDefined(typeof(LogEventLevel), level)) { validLogType = true; logsAfterLevelFilters.AddRange(filteredLogs.Where(x => string.Equals(x.Level.ToString(), level, StringComparison.InvariantCultureIgnoreCase))); } else { validLogType = false; } } if (validLogType) { filteredLogs = logsAfterLevelFilters; } } long totalRecords = filteredLogs.Count; //Order By, Skip, Take & Select var logMessages = filteredLogs .OrderBy(l => l.Timestamp, orderDirection) .Skip(pageSize * (pageNumber - 1)) .Take(pageSize) .Select(x => new LogMessage { Timestamp = x.Timestamp, Level = x.Level, MessageTemplateText = x.MessageTemplate.Text, Exception = x.Exception?.ToString(), Properties = x.Properties, RenderedMessage = x.RenderMessage() }); return new PagedResult(totalRecords, pageNumber, pageSize) { Items = logMessages }; } private static void EnsureFileExists(string path, string contents) { var absolutePath = IOHelper.MapPath(path); if (System.IO.File.Exists(absolutePath)) return; using (var writer = System.IO.File.CreateText(absolutePath)) { writer.Write(contents); } } } }