提交 8b32eebf 编写于 作者: 麦壳饼's avatar 麦壳饼

move diagnostics and storage

上级 f1a99637
......@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using IoTSharp.Data;
using IoTSharp.Handlers;
using IoTSharp.MQTT;
using IoTSharp.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MQTTnet;
......
using System;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace IoTSharp.Diagnostics.Log
{
public class LogEntry
{
public LogEntry(DateTime timestamp, LogLevel level, string source, string message, string exception)
{
Timestamp = timestamp;
Level = level;
Source = source;
Message = message;
Exception = exception;
}
public DateTime Timestamp { get; }
[JsonConverter(typeof(StringEnumConverter))]
public LogLevel Level { get; }
public string Source { get; }
public string Message { get; }
public string Exception { get; }
}
}
\ No newline at end of file
namespace IoTSharp.Diagnostics.Log
{
public class LogEntryFilter
{
public bool IncludeInformations { get; set; }
public bool IncludeWarnings { get; set; }
public bool IncludeErrors { get; set; }
public int TakeCount { get; set; }
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using IoTSharp.Storage;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace IoTSharp.Diagnostics.Log
{
public class LogService : IHostedService
{
private readonly LinkedList<LogEntry> _logEntries = new LinkedList<LogEntry>();
private readonly SystemStatusService _systemStatusService;
private readonly LogServiceOptions _options;
private int _informationsCount;
private int _warningsCount;
private int _errorsCount;
public LogService(StorageService storageService, SystemStatusService systemStatusService)
{
_systemStatusService = systemStatusService ?? throw new ArgumentNullException(nameof(systemStatusService));
if (!storageService.TryReadOrCreate(out _options, LogServiceOptions.Filename))
{
_options = new LogServiceOptions();
}
}
public void Publish(DateTime timestamp, LogLevel logLevel, string source, string message, Exception exception)
{
var newLogEntry = new LogEntry(timestamp, logLevel, source, message, exception?.ToString());
lock (_logEntries)
{
if (newLogEntry.Level == LogLevel.Error)
{
_errorsCount++;
}
else if (newLogEntry.Level == LogLevel.Warning)
{
_warningsCount++;
}
else if (newLogEntry.Level == LogLevel.Information)
{
_informationsCount++;
}
_logEntries.AddFirst(newLogEntry);
if (_logEntries.Count > _options.MessageCount)
{
var removedLogEntry = _logEntries.Last.Value;
if (removedLogEntry.Level == LogLevel.Error)
{
_errorsCount--;
}
else if (removedLogEntry.Level == LogLevel.Warning)
{
_warningsCount--;
}
else if (removedLogEntry.Level == LogLevel.Information)
{
_informationsCount--;
}
_logEntries.RemoveLast();
}
UpdateSystemStatus();
}
}
public void Clear()
{
lock (_logEntries)
{
_logEntries.Clear();
_informationsCount = 0;
_warningsCount = 0;
_errorsCount = 0;
UpdateSystemStatus();
}
}
public List<LogEntry> GetEntries(LogEntryFilter filter)
{
if (filter == null) throw new ArgumentNullException(nameof(filter));
var logEntries = new List<LogEntry>();
lock (_logEntries)
{
foreach (var logEntry in _logEntries)
{
if (logEntry.Level == LogLevel.Information && !filter.IncludeInformations)
{
continue;
}
if (logEntry.Level == LogLevel.Warning && !filter.IncludeWarnings)
{
continue;
}
if (logEntry.Level == LogLevel.Error && !filter.IncludeErrors)
{
continue;
}
logEntries.Add(logEntry);
if (logEntries.Count >= filter.TakeCount)
{
break;
}
}
}
return logEntries;
}
private void UpdateSystemStatus()
{
_systemStatusService.Set("log.informations_count", _informationsCount);
_systemStatusService.Set("log.warnings_count", _warningsCount);
_systemStatusService.Set("log.errors_count", _errorsCount);
}
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}
using System;
using Microsoft.Extensions.Logging;
namespace IoTSharp.Diagnostics.Log
{
public class LogServiceLogger : ILogger
{
private readonly LogService _logService;
private readonly string _categoryName;
public LogServiceLogger(LogService logService, string categoryName)
{
_logService = logService ?? throw new ArgumentNullException(nameof(logService));
_categoryName = categoryName;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
if (!IsEnabled(logLevel))
{
return;
}
_logService.Publish(DateTime.UtcNow, logLevel, _categoryName, formatter(state, exception), exception);
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel >= LogLevel.Information;
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
}
}
using System;
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
namespace IoTSharp.Diagnostics.Log
{
public class LogServiceLoggerProvider : ILoggerProvider
{
private readonly ConcurrentDictionary<string, ILogger> _loggers = new ConcurrentDictionary<string, ILogger>();
private readonly LogService _logService;
public LogServiceLoggerProvider(LogService logService)
{
_logService = logService ?? throw new ArgumentNullException(nameof(logService));
}
public ILogger CreateLogger(string categoryName)
{
if (categoryName == null)
{
categoryName = string.Empty;
}
lock (_loggers)
{
if (_loggers.TryGetValue(categoryName, out var logger))
{
return logger;
}
logger = new LogServiceLogger(_logService, categoryName);
_loggers[categoryName] = logger;
return logger;
}
}
public void Dispose()
{
}
}
}
namespace IoTSharp.Diagnostics.Log
{
public class LogServiceOptions
{
public const string Filename = "LogServiceConfiguration.json";
public int MessageCount { get; set; } = 1000;
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Extensions.Logging;
namespace IoTSharp.Storage
{
public static class StorageExtensions
{
public static bool TryRead<TValue>(this FileInfo file , out TValue value)
{
if (!file.Exists)
{
value = default(TValue);
return false;
}
var json = System.IO.File.ReadAllText(file.FullName);
if (string.IsNullOrEmpty(json))
{
value = default(TValue);
return true;
}
value =Newtonsoft.Json.JsonConvert.DeserializeObject<TValue>(json);
return true;
}
public static bool TryReadText(this FileInfo file, out string value )
{
value = File.ReadAllText(file.FullName);
return true;
}
public static bool TryReadBinText(this FileInfo file, out string value)
{
var filename = file.FullName;
if (!File.Exists(filename))
{
value = null;
return false;
}
value = File.ReadAllText(filename, Encoding.UTF8);
return true;
}
public static bool TryReadRaw(this FileInfo file, out byte[] content)
{
var filename = file.FullName;
if (!File.Exists(filename))
{
content = null;
return false;
}
content = File.ReadAllBytes(filename);
return true;
}
public static bool TryReadOrCreate<TValue>(this FileInfo file, out TValue value) where TValue : class, new()
{
if (!file.TryRead(out value))
{
value = new TValue();
file.Write(value);
return false;
}
return true;
}
public static void Write(this FileInfo file, object value)
{
var filename = file.FullName;
if (!filename.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
{
filename += ".json";
}
var directory = Path.GetDirectoryName(filename);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
if (value == null)
{
File.WriteAllBytes(filename, new byte[0]);
return;
}
var json = Newtonsoft.Json.JsonConvert.SerializeObject(value);
File.WriteAllText(filename, json);
}
public static void WriteRaw(this FileInfo file, byte[] content)
{
var filename = file.FullName;
if (!file.Directory.Exists)
{
file.Directory.Create();
}
File.WriteAllBytes(filename, content ?? new byte[0]);
}
}
}
......@@ -22,6 +22,7 @@ namespace IoTSharp.Diagnostics
public void Set(string uid, object value)
{
if (uid == null) throw new ArgumentNullException(nameof(uid));
_values[uid] = () => value;
......
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using IoTSharp.Extensions;
using IoTSharp.Storage;
using Microsoft.Extensions.Logging;
using MQTTnet;
using MQTTnet.Server;
namespace IoTSharp.MQTT
{
public class MqttServerStorage : IMqttServerStorage
{
private readonly List<MqttApplicationMessage> _messages = new List<MqttApplicationMessage>();
private readonly StorageService _storageService;
private readonly ILogger _logger;
private bool _messagesHaveChanged;
public MqttServerStorage(StorageService storageService, ILogger logger)
{
_storageService = storageService ?? throw new ArgumentNullException(nameof(storageService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public void Start()
{
ParallelTask.Start(SaveRetainedMessagesInternalAsync, CancellationToken.None, _logger);
}
private async Task SaveRetainedMessagesInternalAsync()
{
while (true)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(60)).ConfigureAwait(false);
List<MqttApplicationMessage> messages;
lock (_messages)
{
if (!_messagesHaveChanged)
{
continue;
}
messages = new List<MqttApplicationMessage>(_messages);
_messagesHaveChanged = false;
}
_storageService.Write(messages, "RetainedMqttMessages.json");
_logger.LogInformation($"{messages.Count} retained MQTT messages written to storage.");
}
catch (Exception exception)
{
_logger.LogError(exception, "Error while writing retained MQTT messages to storage.");
}
}
}
public Task SaveRetainedMessagesAsync(IList<MqttApplicationMessage> messages)
{
lock (_messages)
{
_messages.Clear();
_messages.AddRange(messages);
_messagesHaveChanged = true;
}
return Task.CompletedTask;
}
public Task<IList<MqttApplicationMessage>> LoadRetainedMessagesAsync()
{
_storageService.TryRead(out List<MqttApplicationMessage> messages, "RetainedMqttMessages.json");
if (messages == null)
{
messages = new List<MqttApplicationMessage>();
}
return Task.FromResult<IList<MqttApplicationMessage>>(messages);
}
}
}
namespace IoTSharp.MQTT
{
public class MqttServiceOptions
{
public const string Filename = "MqttServiceConfiguration.json";
public bool EnableLogging { get; set; } = false;
public bool PersistRetainedMessages { get; set; } = true;
public int ServerPort { get; set; } = 1883;
}
}
using System;
using IoTSharp.Handlers;
using IoTSharp.Services;
using Microsoft.Extensions.Logging;
using MQTTnet;
using MQTTnet.Client.Options;
......@@ -11,18 +12,16 @@ namespace IoTSharp.MQTT
{
private readonly MqttImportTopicParameters _parameters;
private readonly MqttEventsHandler _mqttService;
private readonly bool _enableMqttLogging;
private readonly ILogger _logger;
private IManagedMqttClient _mqttClient;
public MqttTopicImporter(MqttImportTopicParameters parameters, MqttEventsHandler mqttService, bool enableMqttLogging, ILogger logger)
public MqttTopicImporter(MqttImportTopicParameters parameters, MqttEventsHandler mqttService, ILogger logger)
{
_parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
_mqttService = mqttService ?? throw new ArgumentNullException(nameof(mqttService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_enableMqttLogging = enableMqttLogging;
}
public void Start()
......
......@@ -16,12 +16,11 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace IoTSharp.Handlers
namespace IoTSharp.Services
{
public class MqttEventsHandler
{
......@@ -34,12 +33,8 @@ namespace IoTSharp.Handlers
private readonly OperationsPerSecondCounter _inboundCounter;
private readonly OperationsPerSecondCounter _outboundCounter;
private bool _enableMqttLogging;
private readonly StorageService _storageService;
private readonly SystemCancellationToken _systemCancellationToken;
public MqttEventsHandler(ILogger<MqttEventsHandler> logger, ApplicationDbContext dbContext, IMqttServerEx serverEx, DiagnosticsService diagnosticsService,
StorageService storageService,
SystemStatusService systemStatusService
)
{
......@@ -392,7 +387,7 @@ namespace IoTSharp.Handlers
uid = Guid.NewGuid().ToString("D");
}
var importer = new MqttTopicImporter(parameters, this, _enableMqttLogging, _logger);
var importer = new MqttTopicImporter(parameters, this , _logger);
importer.Start();
lock (_importers)
......
namespace IoTSharp.Storage
{
public static class DefaultFilenames
{
public const string Configuration = "Configuration.json";
public const string Settings = "Settings.json";
public const string Tags = "Tags.json";
public const string Script = "script.py";
}
}
using System;
using System.IO;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace IoTSharp.Storage
{
public class JsonSerializerService
{
private readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy()
},
Formatting = Formatting.Indented,
DateParseHandling = DateParseHandling.None
};
private readonly JsonSerializer _serializer;
public JsonSerializerService()
{
_serializer = JsonSerializer.Create(_serializerSettings);
}
public string Serialize(object value)
{
return JsonConvert.SerializeObject(value, _serializerSettings);
}
public TValue Deserialize<TValue>(string json)
{
if (json == null) throw new ArgumentNullException(nameof(json));
using (var streamReader = new StringReader(json))
{
return (TValue)_serializer.Deserialize(streamReader, typeof(TValue));
}
}
public TValue Deserialize<TValue>(byte[] json)
{
if (json == null) throw new ArgumentNullException(nameof(json));
using (var streamReader = new StreamReader(new MemoryStream(json), Encoding.UTF8))
{
return (TValue)_serializer.Deserialize(streamReader, typeof(TValue));
}
}
public bool TryDeserializeFile<TContent>(string filename, out TContent content)
{
if (filename == null) throw new ArgumentNullException(nameof(filename));
if (!File.Exists(filename))
{
content = default(TContent);
return false;
}
using (var fileStream = File.OpenRead(filename))
using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
{
var jsonReader = new JsonTextReader(streamReader);
content = _serializer.Deserialize<TContent>(jsonReader);
return true;
}
}
}
}
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace IoTSharp.Storage
{
public class StoragePaths
{
public StoragePaths()
{
BinPath = AppDomain.CurrentDomain.BaseDirectory;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
DataPath = Path.Combine(Environment.ExpandEnvironmentVariables("%appData%"), "Wirehome");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
DataPath = Path.Combine("/etc/wirehome");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".wirehome");
}
else
{
throw new NotSupportedException();
}
}
public string BinPath { get; }
public string DataPath { get; }
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Extensions.Logging;
namespace IoTSharp.Storage
{
public class StorageService
{
private readonly JsonSerializerService _jsonSerializerService;
public StorageService(JsonSerializerService jsonSerializerService, ILogger<StorageService> logger)
{
_jsonSerializerService = jsonSerializerService ?? throw new ArgumentNullException(nameof(jsonSerializerService));
var paths = new StoragePaths();
BinPath = paths.BinPath;
DataPath = paths.DataPath;
if (logger == null) throw new ArgumentNullException(nameof(logger));
logger.Log(LogLevel.Information, $"Bin path = {BinPath}");
logger.Log(LogLevel.Information, $"Data path = {DataPath}");
}
public string BinPath { get; }
public string DataPath { get; }
public void Start()
{
}
public List<string> EnumerateDirectories(string pattern, params string[] path)
{
if (pattern == null) throw new ArgumentNullException(nameof(pattern));
if (path == null) throw new ArgumentNullException(nameof(path));
var directory = Path.Combine(DataPath, Path.Combine(path));
if (!Directory.Exists(directory))
{
return new List<string>();
}
var directories = Directory.EnumerateDirectories(directory, pattern, SearchOption.TopDirectoryOnly).ToList();
for (var i = 0; i < directories.Count; i++)
{
directories[i] = directories[i].Replace(directory, string.Empty).TrimStart(Path.DirectorySeparatorChar);
}
return directories;
}
public List<string> EnumerateFiles(string pattern, params string[] path)
{
if (path == null) throw new ArgumentNullException(nameof(path));
var relativePath = Path.Combine(path);
var directory = Path.Combine(DataPath, relativePath);
if (!Directory.Exists(directory))
{
return new List<string>();
}
var files = Directory.GetFiles(directory, pattern, SearchOption.AllDirectories).ToList();
for (var i = 0; i < files.Count; i++)
{
files[i] = files[i].Replace(directory, string.Empty).TrimStart(Path.DirectorySeparatorChar);
}
return files;
}
public bool TryRead<TValue>(out TValue value, params string[] path)
{
if (path == null) throw new ArgumentNullException(nameof(path));
var filename = Path.Combine(DataPath, Path.Combine(path));
if (!filename.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
{
filename += ".json";
}
if (!File.Exists(filename))
{
value = default(TValue);
return false;
}
var json = File.ReadAllText(filename, Encoding.UTF8);
if (string.IsNullOrEmpty(json))
{
value = default(TValue);
return true;
}
value = _jsonSerializerService.Deserialize<TValue>(json);
return true;
}
public bool TryReadText(out string value, params string[] path)
{
if (path == null) throw new ArgumentNullException(nameof(path));
var filename = Path.Combine(DataPath, Path.Combine(path));
if (!File.Exists(filename))
{
value = null;
return false;
}
value = File.ReadAllText(filename, Encoding.UTF8);
return true;
}
public bool TryReadBinText(out string value, params string[] path)
{
if (path == null) throw new ArgumentNullException(nameof(path));
var filename = Path.Combine(BinPath, Path.Combine(path));
if (!File.Exists(filename))
{
value = null;
return false;
}
value = File.ReadAllText(filename, Encoding.UTF8);
return true;
}
public bool TryReadRaw(out byte[] content, params string[] path)
{
if (path == null) throw new ArgumentNullException(nameof(path));
var filename = Path.Combine(DataPath, Path.Combine(path));
if (!File.Exists(filename))
{
content = null;
return false;
}
content = File.ReadAllBytes(filename);
return true;
}
public bool TryReadOrCreate<TValue>(out TValue value, params string[] path) where TValue : class, new()
{
if (path == null) throw new ArgumentNullException(nameof(path));
if (!TryRead(out value, path))
{
value = new TValue();
Write(value, path);
return false;
}
return true;
}
public void Write(object value, params string[] path)
{
if (path == null) throw new ArgumentNullException(nameof(path));
var filename = Path.Combine(DataPath, Path.Combine(path));
if (!filename.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
{
filename += ".json";
}
var directory = Path.GetDirectoryName(filename);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
if (value == null)
{
File.WriteAllBytes(filename, new byte[0]);
return;
}
var json = _jsonSerializerService.Serialize(value);
File.WriteAllText(filename, json, Encoding.UTF8);
}
public void WriteRaw(byte[] content, params string[] path)
{
if (path == null) throw new ArgumentNullException(nameof(path));
var filename = Path.Combine(DataPath, Path.Combine(path));
var directory = Path.GetDirectoryName(filename);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
File.WriteAllBytes(filename, content ?? new byte[0]);
}
public void WriteText(string value, params string[] path)
{
if (path == null) throw new ArgumentNullException(nameof(path));
var filename = Path.Combine(DataPath, Path.Combine(path));
var directory = Path.GetDirectoryName(filename);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
File.WriteAllText(filename, value ?? string.Empty, Encoding.UTF8);
}
public void DeleteFile(params string[] path)
{
if (path == null) throw new ArgumentNullException(nameof(path));
var fullPath = Path.Combine(DataPath, Path.Combine(path));
if (File.Exists(fullPath))
{
File.Delete(fullPath);
}
}
public void DeleteDirectory(params string[] path)
{
if (path == null) throw new ArgumentNullException(nameof(path));
var fullPath = Path.Combine(DataPath, Path.Combine(path));
if (Directory.Exists(fullPath))
{
Directory.Delete(fullPath, true);
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册