2018-08-31 21:57:51 +01:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using Serilog.Events;
|
|
|
|
|
using Serilog.Filters.Expressions;
|
|
|
|
|
using Umbraco.Core.Models;
|
|
|
|
|
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Core.Logging.Viewer
|
|
|
|
|
{
|
|
|
|
|
public abstract class LogViewerSourceBase : ILogViewer
|
|
|
|
|
{
|
2018-09-19 21:22:10 +01:00
|
|
|
private static readonly string expressionOperators = "()+=*<>%-";
|
|
|
|
|
|
2018-08-31 21:57:51 +01:00
|
|
|
public abstract IEnumerable<LogEvent> GetAllLogs(DateTimeOffset startDate, DateTimeOffset endDate);
|
2018-09-12 11:35:51 +01:00
|
|
|
|
|
|
|
|
public abstract IEnumerable<SavedLogSearch> GetSavedSearches();
|
|
|
|
|
|
2018-09-12 16:14:42 +01:00
|
|
|
public abstract IEnumerable<SavedLogSearch> AddSavedSearch(string name, string query);
|
|
|
|
|
|
2018-09-14 15:17:12 +01:00
|
|
|
public abstract IEnumerable<SavedLogSearch> DeleteSavedSearch(string name, string query);
|
|
|
|
|
|
2018-08-31 21:57:51 +01:00
|
|
|
public int GetNumberOfErrors(DateTimeOffset startDate, DateTimeOffset endDate)
|
|
|
|
|
{
|
|
|
|
|
var logs = GetAllLogs(startDate, endDate);
|
|
|
|
|
return logs.Count(x => x.Level == LogEventLevel.Fatal || x.Level == LogEventLevel.Error || x.Exception != null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public LogLevelCounts GetLogLevelCounts(DateTimeOffset startDate, DateTimeOffset endDate)
|
|
|
|
|
{
|
|
|
|
|
var logs = GetAllLogs(startDate, endDate);
|
|
|
|
|
return new LogLevelCounts
|
|
|
|
|
{
|
|
|
|
|
Information = logs.Count(x => x.Level == LogEventLevel.Information),
|
|
|
|
|
Debug = logs.Count(x => x.Level == LogEventLevel.Debug),
|
|
|
|
|
Warning = logs.Count(x => x.Level == LogEventLevel.Warning),
|
|
|
|
|
Error = logs.Count(x => x.Level == LogEventLevel.Error),
|
|
|
|
|
Fatal = logs.Count(x => x.Level == LogEventLevel.Fatal)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-07 21:43:44 +01:00
|
|
|
public IEnumerable<LogTemplate> GetMessageTemplates(DateTimeOffset startDate, DateTimeOffset endDate)
|
2018-08-31 21:57:51 +01:00
|
|
|
{
|
|
|
|
|
var logs = GetAllLogs(startDate, endDate);
|
|
|
|
|
var templates = logs.GroupBy(x => x.MessageTemplate.Text)
|
2018-09-07 21:43:44 +01:00
|
|
|
.Select(g => new LogTemplate { MessageTemplate = g.Key, Count = g.Count() })
|
|
|
|
|
.OrderByDescending(x => x.Count);
|
2018-08-31 21:57:51 +01:00
|
|
|
|
|
|
|
|
return templates;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public PagedResult<LogMessage> GetLogs(DateTimeOffset startDate, DateTimeOffset endDate,
|
|
|
|
|
int pageNumber = 1, int pageSize = 100,
|
|
|
|
|
Direction orderDirection = Direction.Descending,
|
2018-09-03 21:29:13 +01:00
|
|
|
string filterExpression = null,
|
|
|
|
|
string[] logLevels = null)
|
2018-08-31 21:57:51 +01:00
|
|
|
{
|
|
|
|
|
//Get all logs into memory (Not sure this good or not)
|
2018-09-18 21:50:31 +01:00
|
|
|
var allLogs = GetAllLogs(startDate, endDate);
|
|
|
|
|
var logs = allLogs;
|
2018-08-31 21:57:51 +01:00
|
|
|
|
2018-09-19 21:22:10 +01:00
|
|
|
//If we have a filter expression check it and apply
|
2018-08-31 21:57:51 +01:00
|
|
|
if (string.IsNullOrEmpty(filterExpression) == false)
|
|
|
|
|
{
|
|
|
|
|
Func<LogEvent, bool> filter = null;
|
2018-09-18 21:50:31 +01:00
|
|
|
|
2018-09-19 21:22:10 +01:00
|
|
|
// If the expression is one word and doesn't contain a serilog operator then we can perform a like search
|
|
|
|
|
if (!filterExpression.Contains(" ") && !filterExpression.ContainsAny(expressionOperators.Select(c => c)))
|
2018-09-11 15:06:35 +01:00
|
|
|
{
|
2018-09-18 21:50:31 +01:00
|
|
|
filter = PerformMessageLikeFilter(filterExpression);
|
2018-09-11 15:06:35 +01:00
|
|
|
}
|
2018-09-19 21:22:10 +01:00
|
|
|
else // check if it's a valid expression
|
2018-09-11 15:06:35 +01:00
|
|
|
{
|
2018-09-19 21:22:10 +01:00
|
|
|
// If the expression evaluates then make it into a filter
|
|
|
|
|
if (FilterLanguage.TryCreateFilter(filterExpression, out Func<LogEvent, object> eval, out string error))
|
|
|
|
|
{
|
|
|
|
|
filter = evt => true.Equals(eval(evt));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//Assume the expression was a search string and make a Like filter from that
|
|
|
|
|
filter = PerformMessageLikeFilter(filterExpression);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-18 21:50:31 +01:00
|
|
|
|
2018-09-19 21:22:10 +01:00
|
|
|
if (filter != null)
|
|
|
|
|
{
|
2018-09-18 21:50:31 +01:00
|
|
|
logs = FilterLogs(logs, filter);
|
2018-09-11 15:06:35 +01:00
|
|
|
}
|
2018-08-31 21:57:51 +01:00
|
|
|
}
|
|
|
|
|
|
2018-09-03 21:29:13 +01:00
|
|
|
//This is user used the checkbox UI to toggle which log levels they wish to see
|
|
|
|
|
//If an empty array - its implied all levels to be viewed
|
2018-09-11 15:06:35 +01:00
|
|
|
if (logLevels.Length > 0)
|
2018-09-03 21:29:13 +01:00
|
|
|
{
|
|
|
|
|
var logsAfterLevelFilters = new List<LogEvent>();
|
|
|
|
|
foreach (var level in logLevels)
|
|
|
|
|
{
|
|
|
|
|
logsAfterLevelFilters.AddRange(logs.Where(x => x.Level.ToString() == level));
|
|
|
|
|
}
|
|
|
|
|
logs = logsAfterLevelFilters;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-31 21:57:51 +01:00
|
|
|
long totalRecords = logs.Count();
|
|
|
|
|
long pageIndex = pageNumber - 1;
|
|
|
|
|
|
|
|
|
|
//Order By, Skip, Take & Select
|
2018-09-19 21:22:10 +01:00
|
|
|
var logMessages = logs
|
2018-08-31 21:57:51 +01:00
|
|
|
.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<LogMessage>(totalRecords, pageNumber, pageSize)
|
|
|
|
|
{
|
|
|
|
|
Items = logMessages
|
|
|
|
|
};
|
|
|
|
|
}
|
2018-09-18 21:50:31 +01:00
|
|
|
|
|
|
|
|
private Func<LogEvent, bool> PerformMessageLikeFilter(string filterExpression)
|
|
|
|
|
{
|
|
|
|
|
var filterSearch = $"@Message like '%{FilterLanguage.EscapeLikeExpressionContent(filterExpression)}%'";
|
|
|
|
|
if (FilterLanguage.TryCreateFilter(filterSearch, out var eval, out var error))
|
|
|
|
|
{
|
|
|
|
|
return evt => true.Equals(eval(evt));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEnumerable<LogEvent> FilterLogs(IEnumerable<LogEvent> logs, Func<LogEvent, bool> filter)
|
|
|
|
|
{
|
|
|
|
|
if (filter != null)
|
|
|
|
|
{
|
|
|
|
|
logs = logs.Where(filter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return logs;
|
|
|
|
|
}
|
2018-08-31 21:57:51 +01:00
|
|
|
}
|
|
|
|
|
}
|