云服务技术的应用已十分常见,不仅仅指将项目部署在公有云,我们还可以利用云服务厂商提供的各种技术,来提高系统性能,做好站点监控预警等。本篇博客为大家介绍阿里云日志服务。
传统日志记录
我们可能会在项目数据库当中创建一些日志表,比如:客户信息查看记录,核心信息修改日志等。通常这些日志内容价值较低,而且属于只读,但是存放在sql server, my sql, pgsql等数据库当中,不仅会占用大量系统资源,而且伴随着数据量的增加,查询效率也会降低,并且可能需要dba定期清理维护等。
云日志服务
其实众多云服务厂商都有提供相当廉价的日志存储服务,比如阿里云日志,azure table storage
我们只需要对相关sdk稍加封装,即可在项目中使用。
阿里云日志介绍
- 阿里云日志属于NOSQL存储,意味着你可以任意新增或删除日志字段,而不需要像关系型数据库那样必须进行数据库表结构调整。
- 可以直接登录阿里云控制台进行查看,按照语法进行日志查询,并且可以对日志进行导出,也可以使用sdk,在程序中进行日志查询。
- 根据官方介绍,支持千亿级数据秒级查询,性能远高于关系型数据库。
- 支持冷热存储,通常热存储主要应用于查询频率较高的场景
- 支持日志自动过期机制,你可以指定日志记录的存活时间,不需要手动维护。
常用概念
- Project 项目,相当于一个数据库
- LogStore 日志库,相当于数据库中的表
- Shard 分区,相当于日志表的物理分区,分区的目的是为了提高日志写入/读取效率。每个分区可以提供5 MB/s或500次/s的写入能力,或者10 MB/s或100次/s读取能力。分区可以分裂或者合并,可以将LogStore 设置为自动分裂。
- Topic 主题,日志表的逻辑分区,当不同模块的日志被采集到的同一个Logstore中后,可通过Topic进行区分。
- LogGroup 日志组,是使用sdk写入日志的最小单位。它可以包含多条日志记录,可以指定一个topic和source(日志来源,如产生日志的服务器IP地址)
- Log 日志,日志服务采用半结构化的数据模式定义一条日志,包含Topic、Time、Content、Source和Tags五个数据域。通常我们需要将一条日志内容拆分为键值对的形式保存于Content当中。
阿里云日志索引
日志保存到LogStore之后,必须开启索引才能进行查询。
阿里云日志索引分为全文索引-IndexLineInfo和字段索引-IndexKeyInfo两种类型。
- 全文索引通过设置分词符,来为日志提供查询功能。设置了全文索引之后,你可以直接在查询框输入: error, 来查询包含error的日志记录
- 字段索引支持text,long,double和json等四种类型。可以缩小查询范围,比如:level:error。如果要使用统计分析等功能,则必须开启字段索引。
阿里云日志在.NET 项目中的使用
- 安装Nuget包:Aliyun.SLS.SDK
- 在Startup中注册
services.AddSingleton<ILogServiceClient>(sp => LogServiceClientBuilders.HttpBuilder
.Credential(Configuration["AliyunLog:AccessKeyId"], Configuration["AliyunLog:AccessKeySecret"])
.Endpoint(Configuration["AliyunLog:Endpoint"], Configuration["AliyunLog:Project"])
.Build());
可以将插入,批量插入,查询等操作封装一下,使用起来更方便
例如安居客房源发布之后,将图片处理状态回调信息写入阿里云日志
[HttpPost("picture/callback")]
public Task<string> PictureStatusCallbackAsync([FromBody] PictureCallBackDto pictureCallBackDto)
{
return _logService.InsertLogAsync(PictureCallBackDto.LogStoreName, pictureCallBackDto);
}
[HttpGet("log/query")]
public Task<IList<IDictionary<string, string>>> QueryPictureCallBackDto(DateTime from, DateTime to)
{
return _logService.QueryAsync(PictureCallBackDto.LogStoreName, from, to);
}
using Aliyun.Api.LogService;
using Aliyun.Api.LogService.Domain.Log;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Core.Push._58.API.Infrastructure.LogService
{
public class AliyunLogService : IAliyunLogService
{
private readonly ILogServiceClient _logServiceClient;
private readonly DateTime UtcTime = new DateTime(1970, 1, 1);
public AliyunLogService(ILogServiceClient logServiceClient)
{
_logServiceClient = logServiceClient;
}
public async Task<IList<IDictionary<string, string>>> QueryAsync(string logStoreName, DateTime from, DateTime to)
{
var response = await _logServiceClient.GetLogsAsync(
new GetLogsRequest(logStoreName, (int)(from - UtcTime).TotalSeconds, (int)(to - UtcTime).TotalSeconds)
{
Offset = 1,
Line = 100,
});
return response.IsSuccess ? response.Result.Logs : throw new Exception(response.Error.ErrorMessage);
}
public async Task<string> InsertLogAsync<T>(string logStoreName, T item)
{
var response = await _logServiceClient.PostLogStoreLogsAsync(new PostLogsRequest(logStoreName, CreateGroupInfo(item)));
return response.IsSuccess ? "保存成功" : response.Error.ErrorMessage;
}
public async Task<string> BatchInsertLogAsync<T>(string logStoreName, List<T> items)
{
var response = await _logServiceClient.PostLogStoreLogsAsync(new PostLogsRequest(logStoreName, BatchCreateGroupInfo(items)));
return response.IsSuccess ? await response.ReadAsAsync<string>() : response.Error.ErrorMessage;
}
private LogGroupInfo CreateGroupInfo<T>(T item)
{
var group = new LogGroupInfo
{
Source = "58-picture-callback-log",
Topic = DateTime.Now.ToString("yyyy-MM-dd")
};
group.Logs.Add(CreateLogInfo(item));
return group;
}
private LogGroupInfo BatchCreateGroupInfo<T>(List<T> items)
{
var group = new LogGroupInfo();
group.Logs = BatchCreateLogInfo(items);
return group;
}
private LogInfo CreateLogInfo<T>(T item)
{
var logInfo = new LogInfo { Time = DateTime.Now };
foreach(var property in typeof(T).GetProperties())
{
if (property.PropertyType.IsPrimitive || property.PropertyType.Equals(typeof(string)))
{
logInfo.Contents.Add(property.Name, property.GetValue(item)?.ToString());
continue;
}
logInfo.Contents.Add(property.Name, JsonConvert.SerializeObject(property.GetValue(item)));
}
return logInfo;
}
private List<LogInfo> BatchCreateLogInfo<T>(List<T> items)
{
var logInfos = new List<LogInfo>();
var propertys = typeof(T).GetProperties();
foreach(var item in items)
{
var logInfo = new LogInfo { Time = DateTime.Now };
foreach (var property in propertys)
{
if (property.PropertyType.IsPrimitive || property.PropertyType.Equals(typeof(string)))
{
logInfo.Contents.Add(property.Name, property.GetValue(item)?.ToString());
continue;
}
logInfo.Contents.Add(property.Name, JsonConvert.SerializeObject(property.GetValue(item)));
}
}
return logInfos;
}
}
}