Unity应用架构设计(13)——日志组件的实施
对于应用程序而言,日志是非常重要的功能,通过日志,我们可以跟踪应用程序的数据状态,记录Crash的日志可以帮助我们分析应用程序崩溃的原因,我们甚至可以通过日志来进行性能的监控。总之,日志的好处很多,特别是对Release之后的线上版本进行异常的跟踪。
日志存储的分类
在平常开发时,我们通常喜欢在Debug模式下进行调试,通过断点,可以跟踪数据的变化。除了调试,另一种直观的方式是使用控制台输出,比如Java的system.out.println(),.NET的Console.WriteLine(),Swift的print()等等。在Untiy中,为我们提供了Debug.Log()方式来记录。
而对于线上的版本,上述两种调试都不行,那我们怎么来跟踪数据呢?
从日志的存储分类上来看,可以分为四类:控制台,文件系统,数据库,第三方平台
- 控制台:本地开发时使用,记录数据和跟踪执行过程,方便直观
 - 文件系统:可以是一些用户行为性的日志,这些文件可以被用来监控执行时间,进行性能的分析,如果用户同意,则将这些日志传到服务器上
 - 数据库:记录了一些异常日志,也就是Catch了之后的行为,每次用户登录时,传到服务器,帮助分析原因
 - 第三方平台:比如友盟等,当应用闪退时,Crash原因会记录在友盟中,可以通过DashBoard查看
 
日志组件的设计
为了可以更加灵活的跟踪线上的变化,可以使用第三方的Analysis,也可以自建日志组件。我偏向于混合使用,所以接下来,谈谈一个日志组件的基本设计理念,如下图所示:

从上图可以看出,整个入口由工厂LogFactory来创建LogStrategy子类实例,LogStrategy是个抽象的模板类,定义了公共的处理方法,但并不知道怎样写日志(比如是写入到数据库呢还是到文件),写日志的行为交给子类去完成。
日志组件的实施
有了日志组件的设计图,接下来就是将理念落实到行动,让我们来实现它吧!
LogFactory是一个简单工厂,封装创建LogStrategy对象的代码。
public class LogFactory
{
    public static LogFactory Instance=new LogFactory();
    private LogFactory(){}
    private readonly Dictionary<string,LogStrategy> _strategies=new Dictionary<string, LogStrategy>()
    {
        {typeof(ConsoleLogStrategy).Name,new ConsoleLogStrategy() },
        {typeof(FileLogStrategy).Name,new FileLogStrategy() },
        {typeof(DatabaseLogStrategy).Name,new DatabaseLogStrategy() }
    };
    public LogStrategy Resolve<T>() where T:LogStrategy
    {
        return _strategies[typeof(T).Name];
    }
}
LogFactory内部定义了一个字典,Key为LogStrategy子类的类名,Value为具体的LogStrategy对象。通过一个公共接口Resolve<T>来获取相关对象。
使用字典比switch..case更直观,也更加容易扩展其他选项。更重要的是,不会对公共接口Resolve<T>进行修改。
LogStrategy是一个抽象类,即模板类。
它定义了一个公共的API,即Log。在方法Log中,定义了一些对内容的公共操作,因为对于日志来说,不管是记录在数据库还是文件系统,都将对内容拼接上设备类型、设备名称、操作系统、创建时间等基本信息。
同时还定义了一个抽象方法RecordMessage,对于需要写入的类型(文件系统Or数据库Or控制台)延迟到子类决定。
public abstract class LogStrategy
{
    private readonly StringBuilder _messageBuilder=new StringBuilder();
    protected IContentWriter Writer { get; set; }
    /// <summary>
    ///     模板方法
    /// </summary>
    protected abstract void RecordMessage(string message);
    protected abstract void SetContentWriter();
    /// <summary>
    ///     公共的API
    /// </summary>
    public void Log(string message,bool verbose=false)
    {
        if (verbose)
        {
            //公共方法
            RecordDateTime();
            RecordDeviceModel();
            RecordDeviceName();
            RecordOperatingSystem();
        }
        //抽象方法,交由子类实现
        RecordMessage(_messageBuilder.AppendLine(string.Format("Message:{0}", message)).ToString());
    }
    private void RecordDateTime()
    {
        _messageBuilder.AppendLine(string.Format("DateTime:{0}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
    }
    private void RecordDeviceModel()
    {
        _messageBuilder.AppendLine(string.Format("Device Model:{0}",SystemInfo.deviceModel));
    }
    private void RecordDeviceName()
    {
        _messageBuilder.AppendLine(string.Format("Device Name:{0}", SystemInfo.deviceName));
    }
    private void RecordOperatingSystem()
    {
        _messageBuilder
            .AppendLine(string.Format("Operating System:{0}", SystemInfo.operatingSystem))
            .AppendLine();
    }
模板方法模式:在一个方法中定义算法的骨架,而将一些步骤延迟到子类。模板方法使得子类可以在不改变算法的结构下,重新定义算法中的某些步骤。
当在控制台Debug时,我们其实不需要设备类型,设备名称等信息,故公共接口Log提供了一个开关verbose来开启是否需要详细信息,默认为false,即关闭状态。
继承LogStrategy,创建自定义的日志策略
比如实现FileLogStrategy,除了override了 RecordMessage方法之外,还需要提供一个实现了IContentWriter接口的类,你可以直接在RecordMessage方法中写入日志,但可能有一些公共的操作,比如在异步线程,批量将10条数据写到文件或者数据库中,所以提供一个IContentWriter更加容易扩展。
public class FileLogStrategy:LogStrategy
{
    public FileLogStrategy()
    {
        SetContentWriter();
    }
    protected sealed override void SetContentWriter()
    {
        Writer = new FileContentWriter();
    }
    protected override void RecordMessage(string message)
    {
        Writer.Write(message);
    }
}
创建一个
BaseContentWriter,提供了公共的写入方法,比如为了提高性能,文件的IO并不是马上写入文件,而是批量Flush。同样数据库记录日志也是一样,像Unit Of Work那样,批量向数据库写入数据,提高它的吞吐率。
根据需求使用不同的日志类
LogFactory.Instance.Resolve<FileLogStrategy>().Log("Welcome");
小结
不同于服务器端的日志组件,比如Log4J,只需要将日志写在本地文件系统中,客户端的日志相对来说复杂点,因为记录的日志是发生在用户的客户端,所以你必须要想办法把日志传到服务器,比如一些Crash的异常。既然要把日志发回来,在应用闪退时,必须能够持久化到本地,故我们会将日志写到文件系统或者数据库,然后在合适的时候将日志发送到服务器进行分析。当然,你也可以使用第三方的服务,比如友盟或者 Unity Analytics 来分析数据。
源代码托管在Github上,点击此了解
我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan
Unity应用架构设计(13)——日志组件的实施的更多相关文章
- Unity应用架构设计(4)——设计可复用的SubView和SubViewModel(Part 1)
		
『可复用』这个词相信大家都熟悉,通过『可复用』的组件,可以大大提高软件开发效率. 值得注意的事,当我们设计一个可复用的面向对象组件时,需要保证其独立性,也就是我们熟知的『高内聚,低耦合』原则. 组件化 ...
 - Unity应用架构设计(11)——一个网络层的构建
		
对于客户端应用程序,免不了和远程服务打交道.设计一个良好的『服务层』能帮我们规范和分离业务代码,提高生产效率.服务层最核心的模块一定是怎样发送请求,虽然Mono提供了很多C#网络请求类,诸如WebCl ...
 - Unity应用架构设计(9)——构建统一的 Repository
		
谈到 『Repository』 仓储模式,第一映像就是封装了对数据的访问和持久化.Repository 模式的理念核心是定义了一个规范,即接口『Interface』,在这个规范里面定义了访问以及持久化 ...
 - Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 2)
		
MVVM回顾 经过上一篇文章的介绍,相信你对MVVM的设计思想有所了解.MVVM的核心思想就是解耦,View与ViewModel应该感受不到彼此的存在. View只关心怎样渲染,而ViewModel只 ...
 - Unity应用架构设计(12)——AOP思想的实践
		
想象一下,当程序所有的业务逻辑都完成的时候,你可能还来不及喘口气,紧张的测试即将来临.你的Boss告诉你,虽然程序没问题,但某些方法为什么执行这么慢,性能堪忧.领会了Boss的意图之后,漫长的排查问题 ...
 - Unity应用架构设计(6)——设计动态数据集合ObservableList
		
什么是 『动态数据集合』 ?简而言之,就是当集合添加.删除项目或者重置时,能提供一种通知机制,告诉UI动态更新界面.有经验的程序员脑海里迸出的第一个词就是 ObservableCollection.没 ...
 - Unity应用架构设计(4)——设计可复用的SubView和SubViewModel(Part 2)
		
在我们设计和开发应用程序时,经常要用到控件.比如开发一个客户端WinForm应用程序时,微软就为我们提供了若干控件,这些控件为我们提供了可被定制的属性和事件.属性可以更改它的外观,比如背景色,标题等, ...
 - Unity应用架构设计(2)——使用中介者模式解耦ViewModel之间通信
		
当你开发一个客户端应用程序的时候,往往一个单页会包含很多子模块,在不同的平台下,这些子模块又被叫成子View(视图),或者子Component(组件).越是复杂的页面,被切割出来的子模块就越多,子模块 ...
 - Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 1)
		
初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到 ...
 
随机推荐
- 【JAVAWEB学习笔记】15_request
			
HttpServletRequest 学习目标 案例一.完成用户注册 案例二.完成登录错误信息的回显 1.HttpServletRequest概述 我们在创建Servlet时会覆盖service()方 ...
 - 谈一谈JDK8的函数式编程 (一)
			
系列之前我想说的 最近有一段时间没写博客了,这几天回到学校,才闲下来,决定写一写最近学习到的只是,既是为了分享,也是为了巩固.之前看到过一篇调查,文章的数据是学习新知识,光是看只能获得大约5%,然 ...
 - object-fit 解决图片指定大小被压缩问题
			
object-fit 解决图片指定大小被压缩问题 第一次遇到这个属性,是在给video 写 poster的时候,选取的作为poster的img的尺寸有点小,导致video播放器两边有留白.在控制台查看 ...
 - Linux Namespaces机制——实现
			
转自:http://www.cnblogs.com/lisperl/archive/2012/05/03/2480573.html 由于Linux内核提供了PID,IPC,NS等多个Namespace ...
 - PHP基础入门(一)---世界上最好用的编程语言
			
作为一名程序员,我们应该都听过这样一个梗:PHP编程语言,是世界上最好用的编程语言~~~下面来和大家看一下,什么是PHP↓↓↓ PHP PHP又名超文本预处理器,是一种通用开源脚本语言.PHP主要适用 ...
 - VR上天了!全景商业化落地了!——VR全景智慧城市
			
几年前,VR创业公司SpaceVR就启动了旨在将宇航员视觉体验带给普通人的虚拟现实(VR)项目.SpaceVR计划将VR相机卫星送入太空,并将相机拍摄到的太空视频发送回地球,从而让VR用户身临其境地看 ...
 - WEB前端:浏览器(IE+Chrome+Firefox)常见兼容问题处理--02
			
兼容问题目录 8.IE6不支持固定定位 9.IE6下前面元素浮动,后面元素不浮动后他们之间会有间隙 10.IE6下双边距问题 11.IE67下父级有边框,子级有margin的话会不起作用 12.IE6 ...
 - java中File类中list()和listFiles()方法区别
			
list()和listFiles()方法区别: 1.返回值类型不同:前者为String数组,后者为File对象数组 2.数组中元素内容不同:前者为string类型的[文件名](包含后缀名),后者为Fi ...
 - 与我们息息相关的internet服务(1)---域名服务
			
在起步一个公司,从组建的技术上,可能要准备很多东西,其中一个就是我们熟悉的域名-----域名可以用在邮箱中.网站中等等 域名,听起来很怪的一个词(他类似于电话本的作用),但理解起来很简单,想法也很简单 ...
 - 解析PHP面向对象的三大特征
			
class BenHang extends Card{ /*构造函数与及构造的继承*/ function __construct($cardno,$pwd, $name,$money){ parent ...