使用.NET Core创建服务监视器应用程序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/mzl87/article/details/86365936

目录

介绍

背景

数据库

.NET核心解决方案

ServiceMonitor.Core

ServiceMonitor.Common

约定

观察者

ServiceMonitor.WebApi

仪表板

管理

ServiceMonitor


介绍

本文介绍如何创建服务监视器应用程序,但它是什么?简单来说:它是一个允许监视网络中的服务并保存监视结果到数据库的应用程序,本例中为SQL Server

我知道有很多工具可以提供这个功能,还有更好的工具,可以用钱买,但本文的意图是展示如何使用.NET核心能力来构建开发人员可以扩展以满足自定义要求的应用程序。

基本思想是这样的:有一个以无限方式运行的进程来监视主机,数据库和API; 将监控结果保存在SQL Server数据库中,然后我们可以为最终用户构建一个精美的UI并显示每个服务的状态,我们可以有很多目标进行监控,但最好是允许用户订阅特定服务而不是全部例如,DBA需要观察数据库服务器而不是API开发人员需要观察开发数据库和API等。

还要考虑在开发室中安装大型显示器并观察服务状态,并且最好的情况是使用图表。:)

一个特殊功能可能是让一个通知服务在一个或多个服务失败的情况下为所有管理员发送消息,在这种情况下,服务意味着目标,如主机,数据库,API

在本文中,我们将使用以下服务进行监控:

名称

描述

主机

Ping现有主机

数据库

打开并关闭现有数据库的连接

RESTful API

从现有API中获取一个操作

背景

正如我们之前所说,我们将创建一个应用程序来监视现有目标(主机,数据库,API),因此我们需要掌握有关这些概念的基本知识。

主机将使用ping操作进行监控,因此我们将添加与网络相关的包以执行此操作。

数据库将通过开放和关闭连接进行监视,不使用集成安全性,因为您需要使用凭据模拟服务监视器进程,因此在这种情况下,最好让特定用户与数据库连接,并且只有这样才能避免黑客攻击。

RESTful API将使用REST客户端进行监视,以定位返回简单JSON的操作。

数据库

在存储库内部,有一个名为\ Resources \ Database目录,该目录包含相关的数据库文件,请确保按以下顺序运行以下文件:

文件名

描述

00 - Database.sql

数据库定义

01 - Tables.sql

表定义

02 - Constraints.sql

约束(主键,外键和唯一性)

03 - Rows.sql

初始数据

我们可以在这里找到数据库脚本。

                                                                                                       表说明

描述

EnvironmentCategory

包含环境的所有类别:开发,qa和生产

ServiceCategory

包含服务的所有类别:数据库,rest API,服务器,URLWeb服务

Service

包含所有服务定义

ServiceWatcher

包含C#端的所有组件以执行监视操作

ServiceEnvironment

包含服务和环境的关系,例如我们可以定义一个以不同环境命名的FinanceService服务:开发,qa和生产

ServiceEnvironmentStatus

包含每个环境的每个服务的状态

ServiceEnvironmentStatusLog

包含每个服务环境状态的详细信息

Owner

包含代表所有所有者的应用程序的用户列表

ServiceOwner

包含服务和所有者之间的关系

User

包含观看服务的所有用户

ServiceUser

包含服务和用户之间的关系

请不要忘记我们正在使用在本地计算机上运行的解决方案,资源目录中有一个示例API来执行测试,但您需要更改连接字符串并根据您的上下文添加服务。

另外我不建议在ServiceEnvironment表中公开真实的连接字符串,请向您的DBA请求单个用户只能对目标数据库执行打开连接,以防数据库的安全性成为您的任务,创建特定的用户来执行仅打开与数据库的连接并防止泄露敏感信息。

.NET核心解决方案

现在我们需要为此解决方案定义项目,以获得有关项目范围的清晰概念:

项目名

类型

描述

ServiceMonitor.Core

类库

包含与数据库存储相关的所有定义

ServiceMonitor.Common

类库

包含ServiceMonitor项目的常见定义,例如观察者,序列化器和客户端(REST

ServiceMonitor.WebApi

Web API

包含Web API控制器,用于读取和写入有关监视的信息

ServiceMonitor

控制台应用

包含监控所有服务的过程

ServiceMonitor.Core

该项目包含实体和数据库访问的所有定义,因此我们需要为项目添加以下包:

名称

描述

Microsoft.EntityFrameworkCore.SqlServer

最新版本

通过EF Core提供对SQL Server的访问

该项目包含三个层次:业务逻辑,数据库访问和实体。

DashboardService 类代码:

using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Core.BusinessLayer.Contracts;
using ServiceMonitor.Core.BusinessLayer.Responses;
using ServiceMonitor.Core.DataLayer;
using ServiceMonitor.Core.DataLayer.DataContracts;
using ServiceMonitor.Core.EntityLayer;

namespace ServiceMonitor.Core.BusinessLayer
{
    public class DashboardService : Service, IDashboardService
    {
        public DashboardService(ILogger<DashboardService> logger, ServiceMonitorDbContext dbContext)
            : base(logger, dbContext)
        {
        }

        public async Task<IListResponse<ServiceWatcherItemDto>> GetActiveServiceWatcherItemsAsync()
        {
            Logger?.LogDebug("'{0}' has been invoked", nameof(GetActiveServiceWatcherItemsAsync));

            var response = new ListResponse<ServiceWatcherItemDto>();

            try
            {
                response.Model = await DbContext.GetActiveServiceWatcherItems().ToListAsync();

                Logger?.LogInformation("The service watch items were loaded successfully");
            }
            catch (Exception ex)
            {
                response.SetError(Logger, nameof(GetActiveServiceWatcherItemsAsync), ex);
            }

            return response;
        }

        public async Task<IListResponse<ServiceStatusDetailDto>> GetServiceStatusesAsync(string userName)
        {
            Logger?.LogDebug("'{0}' has been invoked", nameof(GetServiceStatusesAsync));

            var response = new ListResponse<ServiceStatusDetailDto>();

            try
            {
                var user = await DbContext.GetUserAsync(userName);

                if (user == null)
                {
                    Logger?.LogInformation("There isn't data for user '{0}'", userName);

                    return new ListResponse<ServiceStatusDetailDto>();
                }
                else
                {
                    response.Model = await DbContext.GetServiceStatuses(user).ToListAsync();

                    Logger?.LogInformation("The service status details for '{0}' user were loaded successfully", userName);
                }
            }
            catch (Exception ex)
            {
                response.SetError(Logger, nameof(GetServiceStatusesAsync), ex);
            }

            return response;
        }

        public async Task<ISingleResponse<ServiceEnvironmentStatus>> GetServiceStatusAsync(ServiceEnvironmentStatus entity)
        {
            Logger?.LogDebug("'{0}' has been invoked", nameof(GetServiceStatusAsync));

            var response = new SingleResponse<ServiceEnvironmentStatus>();

            try
            {
                response.Model = await DbContext.GetServiceEnvironmentStatusAsync(entity);
            }
            catch (Exception ex)
            {
                response.SetError(Logger, nameof(GetServiceStatusAsync), ex);
            }

            return response;
        }
    }
}

ServiceMonitor.Common

约定

  • IWatcher
  • IWatchResponse
  • ISerializer

IWatcher 接口代码:

using System.Threading.Tasks;

namespace ServiceMonitor.Common.Contracts
{
    public interface IWatcher
    {
        string ActionName { get; }

        Task<WatchResponse> WatchAsync(WatcherParameter parameter);
    }
}

IWatchResponse 接口代码:

namespace ServiceMonitor.Common.Contracts
{
    public interface IWatchResponse
    {
        bool Success { get; set; }

        string Message { get; set; }

        string StackTrace { get; set; }
    }
}

ISerializer 接口代码:

namespace ServiceMonitor.Common.Contracts
{
    public interface ISerializer
    {
        string Serialize<T>(T obj);

        T Deserialze<T>(string source);
    }
}

观察者

这些是实现:

  • DatabaseWatcher
  • HttpRequestWatcher
  • PingWatcher

DatabaseWatcher 类代码:

using System;
using System.Data.SqlClient;
using System.Threading.Tasks;
using ServiceMonitor.Common.Contracts;

namespace ServiceMonitor.Common
{
    public class DatabaseWatcher : IWatcher
    {
        public string ActionName
            => "OpenDatabaseConnection";

        public async Task<WatchResponse> WatchAsync(WatcherParameter parameter)
        {
            var response = new WatchResponse();

            using (var connection = new SqlConnection(parameter.Values["ConnectionString"]))
            {
                try
                {
                    await connection.OpenAsync();

                    response.Success = true;
                }
                catch (Exception ex)
                {
                    response.Success = false;
                    response.Message = ex.Message;
                    response.StackTrace = ex.ToString();
                }
            }

            return response;
        }
    }
}

HttpWebRequestWatcher 类代码:

using System;
using System.Threading.Tasks;
using ServiceMonitor.Common.Contracts;

namespace ServiceMonitor.Common
{
    public class HttpRequestWatcher : IWatcher
    {
        public string ActionName
            => "HttpRequest";

        public async Task<WatchResponse> WatchAsync(WatcherParameter parameter)
        {
            var response = new WatchResponse();

            try
            {
                var restClient = new RestClient();

                await restClient.GetAsync(parameter.Values["Url"]);

                response.Success = true;
            }
            catch (Exception ex)
            {
                response.Success = false;
                response.Message = ex.Message;
                response.StackTrace = ex.ToString();
            }

            return response;
        }
    }
}

PingWatcher 类代码:

using System.Net.NetworkInformation;
using System.Threading.Tasks;
using ServiceMonitor.Common.Contracts;

namespace ServiceMonitor.Common
{
    public class PingWatcher : IWatcher
    {
        public string ActionName
            => "Ping";

        public async Task<WatchResponse> WatchAsync(WatcherParameter parameter)
        {
            var ping = new Ping();

            var reply = await ping.SendPingAsync(parameter.Values["Address"]);

            return new WatchResponse
            {
                Success = reply.Status == IPStatus.Success ? true : false
            };
        }
    }
}

ServiceMonitor.WebApi

这个项目代表服务监视器的RESTful API,所以我们将有两个控制器:DashboardControllerAdministrationController仪表板具有与最终用户结果相关的所有操作,管理包含与保存信息(创建,编辑和删除)相关的所有操作。

仪表板

DashboardController 类代码:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Core.BusinessLayer.Contracts;
using ServiceMonitor.WebApi.Responses;

namespace ServiceMonitor.WebApi.Controllers
{
#pragma warning disable CS1591
    [Route("api/v1/[controller]")]
    [ApiController]
    public class DashboardController : ControllerBase
    {
        protected ILogger Logger;
        protected IDashboardService Service;

        public DashboardController(ILogger<DashboardController> logger, IDashboardService service)
        {
            Logger = logger;
            Service = service;
        }
#pragma warning restore CS1591

        /// <summary>
        /// Gets service watcher items (registered services to watch with service monitor)
        /// </summary>
        /// <returns>A sequence of services to watch</returns>
        [HttpGet("ServiceWatcherItem")]
        [ProducesResponseType(200)]
        [ProducesResponseType(204)]
        [ProducesResponseType(500)]
        public async Task<IActionResult> GetServiceWatcherItemsAsync()
        {
            Logger?.LogDebug("'{0}' has been invoked", nameof(GetServiceWatcherItemsAsync));

            var response = await Service.GetActiveServiceWatcherItemsAsync();

            return response.ToHttpResponse();
        }

        /// <summary>
        /// Gets the details for service watch
        /// </summary>
        /// <param name="id">Service ID</param>
        /// <returns></returns>
        [HttpGet("ServiceStatusDetail/{id}")]
        [ProducesResponseType(200)]
        [ProducesResponseType(204)]
        [ProducesResponseType(500)]
        public async Task<IActionResult> GetServiceStatusDetailsAsync(string id)
        {
            Logger?.LogDebug("'{0}' has been invoked", nameof(GetServiceStatusDetailsAsync));

            var response = await Service.GetServiceStatusesAsync(id);

            return response.ToHttpResponse();
        }
    }
}

管理

AdministrationController 类代码:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Core.BusinessLayer.Contracts;
using ServiceMonitor.WebApi.Requests;
using ServiceMonitor.WebApi.Responses;

namespace ServiceMonitor.WebApi.Controllers
{
#pragma warning disable CS1591
    [Route("api/v1/[controller]")]
    [ApiController]
    public class AdministrationController : ControllerBase
    {
        protected ILogger Logger;
        protected IAdministrationService Service;

        public AdministrationController(ILogger<AdministrationController> logger, IAdministrationService service)
        {
            Logger = logger;
            Service = service;
        }
#pragma warning restore CS1591

        /// <summary>
        /// Saves a result from service watch action
        /// </summary>
        /// <param name="request">Service status result</param>
        /// <returns>Ok if save it was successfully, Not found if service not exists else server internal error</returns>
        [HttpPost("ServiceEnvironmentStatusLog")]
        [ProducesResponseType(200)]
        [ProducesResponseType(404)]
        [ProducesResponseType(500)]
        public async Task<IActionResult> PostServiceStatusLogAsync([FromBody]ServiceEnvironmentStatusLogRequest request)
        {
            Logger?.LogDebug("'{0}' has been invoked", nameof(PostServiceStatusLogAsync));

            var response = await Service
                .CreateServiceEnvironmentStatusLogAsync(request.ToEntity(), request.ServiceEnvironmentID);

            return response.ToHttpResponse();
        }
    }
}

ServiceMonitor

这个项目包含Service Monitor Client的所有对象,在这个项目中,我们添加了Newtonsoft.Json用于JSON序列化的包,在ServiceMonitor.Common中有一个名称为ISerializer的接口,因为我不想强制使用特定的序列化程序,你可以改变它在这个层。:)

ServiceMonitorSerializer 类代码:

using Newtonsoft.Json;
using ServiceMonitor.Common.Contracts;

namespace ServiceMonitor
{
    public class ServiceMonitorSerializer : ISerializer
    {
        public string Serialize<T>(T obj)
            => JsonConvert.SerializeObject(obj);

        public T Deserialze<T>(string source)
            => JsonConvert.DeserializeObject<T>(source);
    }
}

接下来,我们将开始MonitorController类,在这个类中,我们将执行所有观察操作,并通过Service Monitor API 中的AdministrationController将所有结果保存在数据库中。

MonitorController 类代码:

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Common;
using ServiceMonitor.Common.Contracts;
using ServiceMonitor.Models;

namespace ServiceMonitor
{
    public class MonitorController
    {
        public MonitorController(AppSettings appSettings, ILogger logger, IWatcher watcher, RestClient restClient)
        {
            AppSettings = appSettings;
            Logger = logger;
            Watcher = watcher;
            RestClient = restClient;
        }

        public AppSettings AppSettings { get; }

        public ILogger Logger { get; }

        public IWatcher Watcher { get; }

        public RestClient RestClient { get; }

        public async Task ProcessAsync(ServiceWatchItem item)
        {
            while (true)
            {
                try
                {
                    Logger?.LogTrace("{0} - Watching '{1}' for '{2}' environment", DateTime.Now, item.ServiceName, item.Environment);

                    var watchResponse = await Watcher.WatchAsync(new WatcherParameter(item.ToDictionary()));

                    if (watchResponse.Success)
                        Logger?.LogInformation(" Success watch for '{0}' in '{1}' environment", item.ServiceName, item.Environment);
                    else
                        Logger?.LogError(" Failed watch for '{0}' in '{1}' environment", item.ServiceName, item.Environment);

                    var watchLog = new ServiceStatusLog
                    {
                        ServiceID = item.ServiceID,
                        ServiceEnvironmentID = item.ServiceEnvironmentID,
                        Target = item.ServiceName,
                        ActionName = Watcher.ActionName,
                        Success = watchResponse.Success,
                        Message = watchResponse.Message,
                        StackTrace = watchResponse.StackTrace
                    };

                    try
                    {
                        await RestClient.PostJsonAsync(AppSettings.ServiceStatusLogUrl, watchLog);
                    }
                    catch (Exception ex)
                    {
                        Logger?.LogError(" Error on saving watch response ({0}): '{1}'", item.ServiceName, ex.Message);
                    }
                }
                catch (Exception ex)
                {
                    Logger?.LogError(" Error watching service: '{0}': '{1}'", item.ServiceName, ex.Message);
                }

                Thread.Sleep(item.Interval ?? AppSettings.DelayTime);
            }
        }
    }
}

在运行控制台应用程序之前,请确保以下方面:

  1. ServiceMonitor 数据库可用
  2. ServiceMonitor 数据库具有服务类别,服务,服务观察者和用户的信息
  3. ServiceMonitor API可用

我们可以检查url api/v1/Dashboard/ServiceWatcherItems的返回值:

{  
  "message":null,
  "didError":false,
  "errorMessage":null,
  "model":[  
    {  
      "serviceID":1,
      "serviceEnvironmentID":1,
      "environment":"Development",
      "serviceName":"Northwind Database",
      "interval":15000,
      "url":null,
      "address":null,
      "connectionString":"server=(local);database=Northwind;user id=johnd;password=SqlServer2017$",
      "typeName":"ServiceMonitor.Common.DatabaseWatcher, ServiceMonitor.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    },
    {  
      "serviceID":2,
      "serviceEnvironmentID":3,
      "environment":"Development",
      "serviceName":"DNS",
      "interval":3000,
      "url":null,
      "address":"192.168.1.1",
      "connectionString":null,
      "typeName":"ServiceMonitor.Common.PingWatcher, ServiceMonitor.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    },
    {  
      "serviceID":3,
      "serviceEnvironmentID":4,
      "environment":"Development",
      "serviceName":"Sample API",
      "interval":5000,
      "url":"http://localhost:5612/api/values",
      "address":null,
      "connectionString":null,
      "typeName":"ServiceMonitor.Common.HttpWebRequestWatcher, ServiceMonitor.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    }
  ]
}

正如我们所看到的,APIDefaultUser返回所有服务,请记住关于一个用户可以订阅多个服务的概念,显然在此示例中,我们的默认用户被绑定到所有服务但我们可以在ServiceUser表中更改此链接。

Program 类代码:

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Common;
using ServiceMonitor.Common.Contracts;

namespace ServiceMonitor
{
    class Program
    {
        private static ILogger logger;
        private static readonly AppSettings appSettings;

        static Program()
        {
            logger = LoggingHelper.GetLogger<Program>();

            var builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");

            var configuration = builder.Build();

            appSettings = new AppSettings
            {
                ServiceWatcherItemsUrl = configuration["serviceWatcherItemUrl"],
                ServiceStatusLogUrl = configuration["serviceStatusLogUrl"],
                DelayTime = Convert.ToInt32(configuration["delayTime"])
            };
        }

        static void Main(string[] args)
        {
            StartAsync(args).GetAwaiter().GetResult();

            Console.ReadLine();
        }

        static async Task StartAsync(string[] args)
        {
            logger.LogDebug("Starting application...");

            var initializer = new ServiceMonitorInitializer(appSettings);

            try
            {
                await initializer.LoadResponseAsync();
            }
            catch (Exception ex)
            {
                logger.LogError("Error on retrieve watch items: {0}", ex);
                return;
            }

            try
            {
                initializer.DeserializeResponse();
            }
            catch (Exception ex)
            {
                logger.LogError("Error on deserializing object: {0}", ex);
                return;
            }

            foreach (var item in initializer.Response.Model)
            {
                var watcherType = Type.GetType(item.TypeName, true);

                var watcherInstance = Activator.CreateInstance(watcherType) as IWatcher;

                var task = Task.Factory.StartNew(async () =>
                {
                    var controller = new MonitorController(appSettings, logger, watcherInstance, initializer.RestClient);

                    await controller.ProcessAsync(item);
                });
            }
        }
    }
}

一旦我们检查了之前的方面,现在我们继续转向控制台应用程序,控制台输出是这样的:

dbug: ServiceMonitor.Program[0]
      Starting application
sr trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:30 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:30 - Watching 'Northwind Database' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:30 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:35 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:37 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:39 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:42 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:43 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:45 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:47 - Watching 'Northwind Database' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:48 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:48 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:51 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:53 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:54 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
      06/20/2017 23:09:57 - Watching 'DNS' for 'Development' environment

现在我们继续检查数据库中保存的数据,请检查ServiceEnvironmentStatus表,你会得到这样的结果:

ServiceEnvironmentStatusID ServiceEnvironmentID Success WatchCount  LastWatch
-------------------------- -------------------- ------- ----------- -----------------------
1                          4                    1       212         2018-11-22 23:11:34.113
2                          1                    1       78          2018-11-22 23:11:33.370
3                          3                    1       366         2018-11-22 23:11:34.620

(3 row(s) affected)

它是如何一起工作的?控制台应用程序从API中监视所有服务,然后在MonitorController中以无限循环的方式为每个监视项启动一个任务,每个任务都有一个延迟时间,该间隔在服务定义中设置,但是如果没有为间隔定义值,间隔取自AppSettings因此,在执行Watch操作之后,结果将通过API保存在数据库中,并且该过程会自行重复。如果要watch对其他类型执行操作,可以创建自己的Watcher类。

 

原文地址:https://www.codeproject.com/Articles/1165961/Creating-Service-Monitor-Application-with-NET-Core

猜你喜欢

转载自blog.csdn.net/mzl87/article/details/86365936