什么样的日志语句会影响系统的性能

影响数据库性能的主要因素有哪些?_百度知道
影响数据库性能的主要因素有哪些?
1、1、调整数据结构的设计。这一部分在开发信息系统之前完成,程序员需要考虑是否使用ORACLE数据库的分区功能,对于经常访问的数据库表是否需要建立索引等。 2、2、调整应用程序结构设计。这一部分也是在开发信息系统之前完成,程序员在这一步需要考虑应用程序使用什么样的体系结构,是使用传统的Client/Server两层体系结构,还是使用Browser/Web/Database的三层体系结构。不同的应用程序体系结构要求的数据库资源是不同的。 3、3、调整数据库SQL语句。应用程序的执行最终将归结为数据库中的SQL语句执行,因此SQL语句的执行效率最终决定了ORACLE数据库的性能。ORACLE公司推荐使用ORACLE语句优化器(Oracle Optimizer)和行锁管理器(row-level manager)来调整优化SQL语句。 4、4、调整服务器内存分配。内存分配是在信息系统运行过程中优化配置的,数据库管理员可以根据数据库运行状况调整数据库系统全局区(SGA区)的数据缓冲区、日志缓冲区和共享池的大小;还可以调整程序全局区(PGA区)的大小。需要注意的是,SGA区不是越大越好,SGA区过大会占用操作系统使用的内存而引起虚拟内存的页面交换,这样反而会降低系统。 5、5、调整硬盘I/O,这一步是在信息系统开发之前完成的。数据库管理员可以将组成同一个表空间的数据文件放在不同的硬盘上,做到硬盘之间I/O负载均衡。 6、6、调整操作系统参数,例如:运行在UNIX操作系统上的ORACLE数据库,可以调整UNIX数据缓冲池的大小,每个进程所能使用的内存大小等参数。 实际上,上述数据库优化措施之间是相互联系的。ORACLE数据库性能恶化表现基本上都是用户响应时间比较长,需要用户长时间的等待。但性能恶化的原因却是多种多样的,有时是多个因素共同造成了性能恶化的结果,这就需要数据库管理员有比较全面的计算机知识,能够敏感地察觉到影响数据库性能的主要原因所在。另外,良好的数据库管理工具对于优化数据库性能也是很重要的。 ORACLE数据库性能优化工具 常用的数据库性能优化工具有: 1、1、ORACLE数据库在线数据字典,ORACLE在线数据字典能够反映出ORACLE动态运行情况,对于调整数据库性能是很有帮助的。 2、2、操作系统工具,例如UNIX操作系统的vmstat,iostat等命令可以查看到系统系统级内存和硬盘I/O的使用情况,这些工具对于管理员弄清出系统瓶颈出现在什么地方有时候很有用。 3、3、SQL语言跟踪工具(SQL TRACE FACILITY),SQL语言跟踪工具可以记录SQL语句的执行情况,管理员可以使用虚拟表来调整实例,使用SQL语句跟踪文件调整应用程序性能。SQL语言跟踪工具将结果输出成一个操作系统的文件,管理员可以使用TKPROF工具查看这些文件。 4、4、ORACLE Enterprise Manager(OEM),这是一个图形的用户管理界面,用户可以使用它方便地进行数据库管理而不必记住复杂的ORACLE数据库管理的命令。 5、5、EXPLAIN PLAN——SQL语言优化命令,使用这个命令可以帮助程序员写出高效的SQL语言。 ORACLE数据库的系统性能评估 信息系统的类型不同,需要关注的数据库参数也是不同的。数据库管理员需要根据自己的信息系统的类型着重考虑不同的数据库参数。 1、1、在线事务处理信息系统(OLTP),这种类型的信息系统一般需要有大量的Insert、Update操作,典型的系统包括民航机票发售系统、银行储蓄系统等。OLTP系统需要保证数据库的并发性、可靠性和最终用户的速度,这类系统使用的ORACLE数据库需要主要考虑下述参数: l
数据库回滚段是否足够? l
是否需要建立ORACLE数据库索引、聚集、散列? l
系统全局区(SGA)大小是否足够? l
SQL语句是否高效? 2、2、数据仓库系统(Data Warehousing),这种信息系统的主要任务是从ORACLE的海量数据中进行查询,得到数据之间的某些规律。数据库管理员需要为这种类型的ORACLE数据库着重考虑下述参数: l
是否采用B*-索引或者bitmap索引? l
是否采用并行SQL查询以提高查询效率? l
是否采用PL/SQL函数编写存储过程? l
有必要的话,需要建立并行数据库提高数据库的查询效率 SQL语句的调整原则 SQL语言是一种灵活的语言,相同的功能可以使用不同的语句来实现,但是语句的执行效率是很不相同的。程序员可以使用EXPLAIN PLAN语句来比较各种实现方案,并选出最优的实现方案。总得来讲,程序员写SQL语句需要满足考虑如下规则: 1、1、尽量使用索引。试比较下面两条SQL语句: 语句A:SELECT dname, deptno FROM dept WHERE deptno NOT IN
(SELECT deptno FROM emp); 语句B:SELECT dname, deptno FROM dept WHERE NOT EXISTS (SELECT deptno FROM emp WHERE dept.deptno = emp.deptno); 这两条查询语句实现的结果是相同的,但是执行语句A的时候,ORACLE会对整个emp表进行扫描,没有使用建立在emp表上的deptno索引,执行语句B的时候,由于在子查询中使用了联合查询,ORACLE只是对emp表进行的部分数据扫描,并利用了deptno列的索引,所以语句B的效率要比语句A的效率高一些。 2、2、选择联合查询的联合次序。考虑下面的例子: SELECT stuff FROM taba a, tabb b, tabc c WHERE a.acol between :alow and :ahigh AND b.bcol between :blow and :bhigh AND c.ccol between :clow and :chigh AND a.key1 = b.key1 AMD a.key2 = c.key2; 这个SQL例子中,程序员首先需要选择要查询的主表,因为主表要进行整个表数据的扫描,所以主表应该数据量最小,所以例子中表A的acol列的范围应该比表B和表C相应列的范围小。 3、3、在子查询中慎重使用IN或者NOT IN语句,使用where (NOT) exists的效果要好的多。 4、4、慎重使用视图的联合查询,尤其是比较复杂的视图之间的联合查询。一般对视图的查询最好都分解为对数据表的直接查询效果要好一些。 5、5、可以在参数文件中设置SHARED_POOL_RESERVED_SIZE参数,这个参数在SGA共享池中保留一个连续的内存空间,连续的内存空间有益于存放大的SQL程序包。 6、6、ORACLE公司提供的DBMS_SHARED_POOL程序可以帮助程序员将某些经常使用的存储过程“钉”在SQL区中而不被换出内存,程序员对于经常使用并且占用内存很多的存储过程“钉”到内存中有利于提高最终用户的响应时间。 CPU参数的调整 CPU是服务器的一项重要资源,服务器良好的工作状态是在工作高峰时CPU的使用率在90%以上。如果空闲时间CPU使用率就在90%以上,说明服务器缺乏CPU资源,如果工作高峰时CPU使用率仍然很低,说明服务器CPU资源还比较富余。 使用操作相同命令可以看到CPU的使用情况,一般UNIX操作系统的服务器,可以使用sar –u命令查看CPU的使用率,NT操作系统的服务器,可以使用NT的性能管理器来查看CPU的使用率。 数据库管理员可以通过查看v$sysstat数据字典中“CPU used by this session”统计项得知ORACLE数据库使用的CPU时间,查看“OS User level CPU time”统计项得知操作系统用户态下的CPU时间,查看“OS System call CPU time”统计项得知操作系统系统态下的CPU时间,操作系统总的CPU时间就是用户态和系统态时间之和,如果ORACLE数据库使用的CPU时间占操作系统总的CPU时间90%以上,说明服务器CPU基本上被ORACLE数据库使用着,这是合理,反之,说明服务器CPU被其它程序占用过多,ORACLE数据库无法得到更多的CPU时间。 数据库管理员还可以通过查看v$sesstat数据字典来获得当前连接ORACLE数据库各个会话占用的CPU时间,从而得知什么会话耗用服务器CPU比较多。 出现CPU资源不足的情况是很多的:SQL语句的重解析、低效率的SQL语句、锁冲突都会引起CPU资源不足。 1、数据库管理员可以执行下述语句来查看SQL语句的解析情况: SELECT * FROM V$SYSSTAT WHERE NAME IN ('parse time cpu', 'parse time elapsed', 'parse count (hard)'); 这里parse time cpu是系统服务时间,parse time elapsed是响应时间,用户等待时间 waite time = parse time elapsed – parse time cpu 由此可以得到用户SQL语句平均解析等待时间=waite time / parse count。这个平均等待时间应该接近于0,如果平均解析等待时间过长,数据库管理员可以通过下述语句 SELECT SQL_TEXT, PARSE_CALLS, EXECUTIONS FROM V$SQLAREA ORDER BY PARSE_CALLS; 来发现是什么SQL语句解析效率比较低。程序员可以优化这些语句,或者增加ORACLE参数SESSION_CACHED_CURSORS的值。 2、数据库管理员还可以通过下述语句: SELECT BUFFER_GETS, EXECUTIONS, SQL_TEXT FROM V$SQLAREA; 查看低效率的SQL语句,优化这些语句也有助于提高CPU的利用率。 3、3、数据库管理员可以通过v$system_event数据字典中的“latch free”统计项查看ORACLE数据库的冲突情况,如果没有冲突的话,latch free查询出来没有结果。如果冲突太大的话,数据库管理员可以降低spin_count参数值,来消除高的CPU使用率。 内存参数的调整 内存参数的调整主要是指ORACLE数据库的系统全局区(SGA)的调整。SGA主要由三部分构成:共享池、数据缓冲区、日志缓冲区。 1、
共享池由两部分构成:共享SQL区和数据字典缓冲区,共享SQL区是存放用户SQL命令的区域,数据字典缓冲区存放数据库运行的动态信息。数据库管理员通过执行下述语句: select (sum(pins - reloads)) / sum(pins) &Lib Cache&
from v$ 来查看共享SQL区的使用率。这个使用率应该在90%以上,否则需要增加共享池的大小。数据库管理员还可以执行下述语句: select (sum(gets - getmisses - usage - fixed)) / sum(gets) &Row Cache& from v$ 查看数据字典缓冲区的使用率,这个使用率也应该在90%以上,否则需要增加共享池的大小。 2、
数据缓冲区。数据库管理员可以通过下述语句: SELECT name, value
FROM v$sysstat
WHERE name IN ('db block gets', 'consistent gets','physical reads'); 来查看数据库数据缓冲区的使用情况。查询出来的结果可以计算出来数据缓冲区的使用命中率=1 - ( physical reads / (db block gets + consistent gets) )。 这个命中率应该在90%以上,否则需要增加数据缓冲区的大小。 3、
日志缓冲区。数据库管理员可以通过执行下述语句: select name,value from v$sysstat where name in ('redo entries','redo log space requests');查看日志缓冲区的使用情况。查询出的结果可以计算出日志缓冲区的申请失败率: 申请失败率=requests/entries,申请失败率应该接近于0,否则说明日志缓冲区开设太小,需要增加ORACLE数据库的日志缓冲区。
采纳率:32%
为您推荐:
其他类似问题
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。-------------
新增文件夹...
新增文件夹
(多个标签用逗号分隔)
log4j对系统性能的影响.docx
如果在程序运行中输出大量日志,显然会对应用性能造成一定的影响。Log4J对性能的影响程度取决于以下因素:1.日志输出目的地:例如把日志输出到控制台的速度和输出到文件系统的速度是不一样的。2.日志输出格式:例如采用SimpleLayout输出日志消息比采用Patternlayout简单,因此速度更快。3.日志级别:日志级别设置得越低,输出的日志内容越多,对性能的影响越大。在产品测试阶段,可以把日志级
如果在程序运行中输出大量日志,显然会对应用性能造成一定的影响。Log4J对性能的影响程度取决于以下因素:1.日志输出目的地:例如把日志输出到控制台的速度和输出到文件系统的速度是不一样的。2.日志输出格式:例如采用SimpleLayout输出日志消息比采用Patternlayout简单,因此速度更快。3.日志级别:日志级别设置得越低,输出的日志内容越多,对性能的影响越大。在产品测试阶段,可以把日志级别设置得低一些,便于跟踪和调试程序,而在产品发布阶段,应该把日志级别设置得高一些。&&
加载中...!
如果长时间没有加载,请点击
来安装或允许flash插件运行!
下载本文档需要登录,并付出相应积分()。
文件大小:17.80 KB
所需积分:& 3
相关资讯  — 
相关讨论话题  — 
浏览:11360次&& 下载:0次
格式:docx
上传时间: 12:02:52
同类热门文档
12643次浏览 &8次下载
0次浏览 &6次下载
0次浏览 &4次下载
0次浏览 &3次下载
4828次浏览 &0次下载
0次浏览 &0次下载
相关经验 -
& 2人评&11页
& 0人评&33页
& 1人评&12页
& 0人评&6页
& 2人评&9页
OPEN-OPEN, all rights reserved.多缓冲提高日志系统性能
前言:无论什么项目肯定都少不了日志系统,所以一个高性能的日志系统是不可避免的。
本文介绍的是自己用c++11实现的一个简单的多缓冲区日志系统,比较水,仅供参考^_^
日志系统及重要性
单缓冲日志系统模型及缺陷
多缓冲buffer介绍及优势
多缓冲区缺陷
Buffer类设计及分析
Logger类设计及分析
日志系统及重要性:
日志信息对于一个优秀项目来说是非常重要的,因为无论再优秀的软件都有可能产生崩溃或异常,此时,日志系统就能发挥它的作用。
快速定位到错误地点以及错误内容,或者查看最近信息等。
一般来说一个日志系统会分级写日志,比如INFO信息日志(用户的一些操作等),ERROR错误日志(系统崩溃或异常),FAIL失败日志(某项操作失败)等等。
由于日志系统非常重要,它会出现在我们程序的每个角落,所以一个好的日志系统就非常重要了,现存的有许多好的实现比如c++的log4,下面介绍是按自己的思路实现的一个非常简单的日志系统。
单缓冲日志系统模型及缺陷
最简单的日志系统就是单缓冲或者无缓冲的。
无缓冲最简单,在个需要输出日志信息的地点都输出信息到文件并写入磁盘即可,但是注意现在程序一般都是并发执行,多进程或多线程写文件我们要加锁。
这样效率就比较低了,比如你有20个线程在运行,每次输出日志都要先抢到锁然后在输出,并且输出到磁盘本身就很慢,这样不仅输出日志效率低,更可能会影响到程序的运行(会阻塞程序,因为日志输出是无处不在的)。
单缓冲就是我们开辟一块固定大小的空间,每次日志输出都先输出到缓冲中,等到缓冲区满在一次刷新到磁盘上,这样相比较无缓冲效率提高了一些,不用每次都输出到磁盘文件上,待到一定数量再刷新到磁盘上。但是每次输出到日志文件上的线程或进程都要加锁,还是存在一个抢锁的过程,效率也不高。
从上图能看出来,每次写缓冲还是存在抢锁和阻塞的过程,这样效率还是比较低的,相对无缓冲来说,仅仅减少了磁盘IO的次数。但在磁盘IO时,程序依旧会阻塞
磁盘IO依旧是瓶颈
多缓冲 buffer介绍
既然单块缓冲满足不了我们的要求,效率依然比较低,那么我们可以尝试选择多块缓冲来实现。程序只关注当前缓冲,其余多块缓冲交给后台线程来处理。
当前缓冲为我们程序写缓冲Buffer,备用缓冲Buffer为我们提前开辟缓冲,当当前curBuf缓冲满时交换Buffer。如下图
在实际中我们用指针来操控(代码中我使用的std::shared_ptr,只用交换指针即可),交换完毕后如下图
此时,我们可以唤醒后台线程处理已满缓冲,当前缓冲交换后为空,程序可以继续写当前缓冲而不会因为磁盘IO而阻塞。
如果程序写日志速度非常快,我们可以开大缓冲,或者将当前缓冲设置为两块,备用缓冲可设置为多块,在实际编写程序时,因为我用的是list&std::shared_ptr&这种结构来保存,当备用缓冲不够时,会创建一块,然后list会自动push_back,这样慢慢程序会达到最适应自己的缓冲大小。
优势很明显了,我们程序只管写,一切由后台线程来完成,不会阻塞在磁盘IO上。
多缓冲区缺陷
多缓冲区设计是有缺陷的,相比较单缓冲是避免了磁盘IO这一耗时的操作,但如果程序写日志量非常大时,每次写curBuf当前缓冲都要先抢锁,可见效率之低,等待锁的时间耗费非常大。多个线程或进程操作一块或两块缓冲,锁的颗粒度非常大。我们可以尝试减小锁的颗粒度
解决方案可以参考Java的ConcurrentHashMap原理,ConcurrentHashMap是内部建立多个桶,每次hash到不同的桶中,锁只锁相应的桶,那么等于减少了锁的颗粒度,阻塞在锁上的频率也就大大降低。
当前程序Buffer开辟多个,类似多个桶,锁只锁相应的桶即可,减小了锁的颗粒度
这么做算已时间换空间了,然后如果没有这么大需求上面方案即可解决。
Buffer类设计及分析
Buffer类的设计参考了netty中的buffer,一块缓冲,三个标记分别为可读位置readable,可写位置writable,容量capacity。
其实readable和writable位置都为0,capacity为容量大小。
写缓冲时writable移动,读缓冲时readable移动,writable &= capacity。
缓冲区我使用了vector&char&,参考了陈硕前辈的muduo,使用vector&char&一方面它内部和数组是一样的,其次我们还可以借助vector特性来管理它。Buffer类比较简单
class Buffer
static size_t initializeS
explicit Buffer(size_t BufferSize = initializeSize):
readable(0), writable(0)
buffer.resize(BufferSize);
size_t Capacity()
return buffer.capacity();
size_t Size()
void setSize(void)
readable = 0;
writable = 0;
void append(const char* mesg, int len)
strncpy(WritePoint(), mesg, len);
writable +=
size_t avail()
return Capacity()-
char* ReadPoint()
return &buffer[readable];
char* WritePoint()
return &buffer[writable];
size_t ReadAddr()
size_t WriteAddr()
std::vector&char&
Logger类设计及分析
Logger类我的实现遵从与刚才说的多缓冲模型。
curBuf为一块,备用Buffer为两块,并且可自适应改变。
class Logger
static std::shared_ptr&Logger& setLogger();
static std::shared_ptr&Logger& setLogger(size_t bufSize);
static std::shared_ptr&Logger& getLogger();
static void logStream(const char* mesg, int len);
static std::shared_ptr&Logger& myL
static std::shared_ptr&Buffer& curB
static std::list&std::shared_ptr&Buffer&& bufL
static std::shared_ptr&Buffer& useFul();
static std::condition_variable readableB
static int readableN
static std::
static std::thread readT
static void Threadfunc();
static void func();
static bool isHave();
从上面代码可以看出当前Buffer和备用Buffer都用智能指针来管理,我们不用操心资源释放等问题,因为为指针,当前Buffer和备用Buffer交换起来速度非常快。
初始化函数
std::shared_ptr&Logger&
setLogger()
if(myLogger == nullptr)
/* 创建日志类 */
myLogger = std::move(std::make_shared&Logger&());
/* 创建当前Buffer */
curBuf = std::make_shared&Buffer&();
/* 创建两块备用Buffer */
bufList.resize(2);
(*bufList.begin()) = std::make_shared&Buffer&();
(*(++bufList.begin())) = std::make_shared&Buffer&();
return myL
都是由智能指针来管理
useful类,返回一个可用的备用Buffer
std::shared_ptr&Buffer&
auto iter = bufList.begin();
for(; iter != bufList.end(); ++iter)
if((*iter)-&Size() == 0)
if(iter == bufList.end())
std::shared_ptr&Buffer& p = std::make_shared&Buffer&();
bufList.push_back(std::move(p));
这算是自适应过程了,随着程序的运行会返回适应的大小。
logStream写日志类
logStream(const char* mesg, int len)
std::unique_lock&std::mutex& locker(mutex);
if(curBuf-&avail() & len)
curBuf-&append(mesg, len);
auto useBuf = useFul();
curBuf.swap(useBuf);
++readableN
readableBuf.notify_one();
线程主要执行函数
Logger::func()
std::unique_lock&std::mutex& locker(mutex);
auto iter = bufList.begin();
/* 如果备用缓冲并无数据可读,阻塞等待唤醒 */
if(readableNum == 0)
readableBuf.wait(locker, Logger::isHave);
/* 找数据不为空的Buffer */
for(; iter != bufList.end(); ++iter)
if((*iter)-&Size() != 0)
/* 如果到末尾没找到,没有数据可读 */
if(iter == bufList.end())
/* 将满的缓冲写到文件中 */
int fd = open("1.txt", O_RDWR | O_APPEND, 00700);
if(fd & 0)
perror("open error\n");
write(fd, iter-&get(), (*iter)-&Capacity());
/* 清空缓冲 */
bzero(iter-&get(), (*iter)-&Capacity());
/* 归位readable和writable */
(*iter)-&setSize();
/* 可读缓冲数量减1 */
--readableN
仅仅是一个简单的实现,如有更优方案或错误还望指出,谢谢~
没有更多推荐了,
不良信息举报
举报内容:
多缓冲提高日志系统性能
举报原因:
原文地址:
原因补充:
最多只允许输入30个字
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!写给开发者:记录日志的10个建议 - 文章 - 伯乐在线
& 写给开发者:记录日志的10个建议
欢迎在新的一年来到我的博客。在一个巴黎devops maillist上回复了一个关于监控和日志监控之后,我想起了很久以前我的一个博客计划。
尽管在写这篇博文的时候,我是在负责运维工作,不过本文主要是写给开发者的。
对我来说,明白如何记录日志和记录什么,是软件工程师必须明了的最艰巨的任务之一。之所以这么说,是因为这项任务与预测(divination)类似,你不知道当你要调试的时候需要些什么信息……我希望这10个建议能帮助你更好地在应用程序中记录日志,让运维工程师们受益。:)
1. 你不应自己写log
绝对不要,即便是用printf或者是自己写入到log文件,又或自己处理logrotate。请给你的运维同志们省省心,调用标准库或者系统API来完成它。
这样,你可以保证程序的运行与其他系统组件好好相处,把log写到正确的位置或者网络服务上,而不需要专门的系统配置。
假如你要使用系统API,也就是syslog(3),学习好怎么用它。
如果你更喜欢用logging库,在Java里面你有很多选择,例如Log4j,JCL,slf4j和logback。我最喜欢用slf4j和logback的组合,因为它们特别给力,而且相对地容易配置(还允许使用JMX进行配置或者重载配置文件)。
slf4j最好的是你可以修改logging控制台的位置。如果你在编写一个库,这会变得非常重要,因为这可以让库的使用者使用自己的logging控制台而不需要修改你的库。
其他语言当然也有多种logging库,例如ruby的Log4r,stdlib logger,和几近完美的Jordan Sissel’s Ruby-cabin。
如果你想纠结CPU占用问题,那么你不用看这篇文章了。还有,不要把log语句放在紧内部循环体内,否则你永远看不出区别来。
2. 你应在适当级别上进行log
如果你遵循了上述第一点的做法,接下来你要对你程序中每一个log语句使用不同的log级别。其中最困难的一个任务是找出这个log应该是什么级别
以下是我的一些建议:
TRACE level: 如果使用在生产环境中,这是一个代码异味(code smell)。它可以用于开发过程中追踪bug,但不要提交到你的版本控制系统
DEBUG level: 把一切东西都记录在这里。这在debug过程中最常用到。我主张在进入生产阶段前减少debug语句的数量,只留下最有意义的部分,在调试(troubleshooting)的时候激活。
INFO level: 把用户行为(user-driven)和系统的特定行为(例如计划任务…)
NOTICE level: 这是生产环境中使用的级别。把一切不认为是错误的,可以记录的事件都log起来
WARN level: 记录在这个级别的事件都有可能成为一个error。例如,一次调用数据库使用的时间超过了预设时间,或者内存缓存即将到达容量上限。这可以让你适当地发出警报,或者在调试时更好地理解系统在failure之前做了些什么
ERROR level: 把每一个错误条件都记录在这。例如API调用返回了错误,或是内部错误条件
FATAL level: 末日来了。它极少被用到,在实际程序中也不应该出现多少。在这个级别上进行log意味着程序要结束了。例如一个网络守护进程无法bind到socket上,那么它唯一能做的就只有log到这里,然后退出运行。
记住,在你的程序中,默认的运行级别是高度可变的。例如我通常用INFO运行我的服务端代码,但是我的桌面程序用的是DEBUG。这是因为你很难在一台你没有接入权限的机器上进行调试,但你在做用户服务时,比起教他们怎么修改log level再把生成的log发给你,我的做法可以让你轻松得多。当然你可以有其他的做法:)
3. honor the log category
我在第一点中提到的大部分logging库允许指定一个logging类别。它可以分类log信息,并基于logging框架的配置,在最后以某一形式进行log或是不进行。
通常,Java开发者在log语句处使用完整,合格的类名作为类别名。如果你的程序遵循单一职责原则(Single responsibility principle,原文有误),这种模式还不错。
在Java的logging库中,Log类别是按等级划分的,例如在com.daysofwonder.ranking.ELORankingComputation会匹配到顶级的com.daysofwonder.ranking。这可以让运营工程师配置一个对此类别下指定的所有ranking子系统作用的logging。如果需要的话,还可以同时生成子类别的logging配置。
拓展开来,我们讲解一下特定情况下的调试。假设你在做一个应答用户请求的服务端软件(如REST API)。它正在对my.service.api.&apitoken&进行log(其中apitoken用于识别用户)。那么你可以选择对my.service.api类别进行log,记录所有的api,或是对某违规API用户的my.service.api.&bad-user-api-token&进行log。当然这需要系统允许你在运行中修改logging配置。
4. 你应该写有意义的log
这可能是最重要的建议了。没有什么比你深刻理解程序内部,却写出含糊的log更糟了。
在你写日志信息之前,总要提醒自己,有突发事件的时候,你唯一拥有的只有来自log文件,你必须从中明白发生了什么。这可能就是被开除和升职之间的微妙的差距。
当开发者写log的时候,它(log语句)是直接写在代码环境中的,在各种条件中我们应该写入基于当前环境的信息。不幸的是,在log文件中并没有这些环境,这可能导致这些信息无法被理解。
解决这个情况(在写warn和error level时尤为重要)的一个方法是,添加辅助信息到log信息中,如果做不到,那么改为把这个操作的作用写下。
还有,不要让一个log信息的内容基于上一个。这是因为前面的信息可能由于(与当前信息)处于不同的类别或者level而没被写入。更坏的情况是,它因多线程或异步操作,在另一个地方(或是以另一方式)出现。
5. 日志信息应该用英语
这个建议可能有点奇怪,尤其是对法国佬(French guy)来说。我还是认为英语远比法语更简炼,更适应技术语言。如果一个信息里面包含超过50%的英语单词,你有什么理由去用法语写log呢
把英法之争丢一边,下面是这个建议背后的原因:
英语意味着你的log是用ASCII编码的。这非常重要,因为你不会真正知道log信息会发生什么,或是它被归档前经过何种软件层和介质。如果你的信息里面使用了特殊字符集,乃至UTF-8,它可能并不会被正确地显示(render),更糟的是,它可能在传输过程中被损坏,变得不可读。不过这还有个问题,log用户输入时,可能有各种字符集或者编码。
如果你的程序被大多数人使用,而你又没有足够的资源做国际化,英语会成为你的不二之选。如果你有国际化,那么让界面与终端用户更亲近(closer)(这通常不会是你的log)
如果你国际化了你的log(例如所有的warning和error level信息),给他们一个特定的有意义的错误码。这样,用户做与语言无关的搜索,找到相关信息。这种良好的模式已经在虚拟内存(VMS)操作系统中应用了很久,而我必须承认它非常有用。如果你曾经设计过这种模式,你还可以试试这种模式: APP-S-CODE 或者 APP-S-SUB-CODE,它们分别代表:
APP: 应用程序的3字缩写
S: 严重程度的1字缩写(例如D代表debug,I代表info)
SUB: 这个code所从属的应用程序的子部分
CODE: 一个数字代号,指定这个问题中的错误
6. 你应该给log带上上下文
没有什么比这样的log信息更糟的了
Transaction failed
Transaction failed
User operation succeeds
User operation succeeds
又或是API异常时:
java.lang.IndexOutOfBoundsException
java.lang.IndexOutOfBoundsException
没有相应的上下文,这些信息不过是噪音,它们不会对调试过程中有意义的数值或是空间起作用(add value and consume space)。
带上上下文的信息要有价值得多,例如:
Transaction 234632 failed: cc number checksum incorrect
Transaction 234632 failed: cc number checksum incorrect
User 54543 successfully registered e-mail&a href="mailto:"&&/a&
User 54543 successfully registered e-mail&a href="mailto:"&user@domain.com&/a&
IndexOutOfBoundsException: index 12 is greater than collection size 10
IndexOutOfBoundsException: index 12 is greater than collection size 10
在上面这一例子中的异常,如果你想把它传播开, 确保在处理的时候带上与当前level相应的上下文,让调试更简单,如下一个java的例子:
public void storeUserRank(int userId,int rank,String game) {
...deal database ...
} catch (DatabaseException de) {
throw new RankingException("Can't store ranking for user "+userId+" in game "+ game + " because " + de.getMessage() );
public void storeUserRank(int userId,int rank,String game) {&&&& try {&&&&&&&&&&...deal database ...&&&& } catch (DatabaseException de) {&&&&&&&&&&throw new RankingException("Can't store ranking for user "+userId+" in game "+ game + " because " + de.getMessage() );&&&& }}
这样,rank API的上层客户端就可以有足够的上下文信息log这个error。更好的做法是让上下文成为exception的参数,而不是信息,如果需要的话,上层可以对它进行修正(use remediation)。
保留上下文的一个简单方法是使用一些java logging库的MDC实现。MDC是一个每线程关联数组(per thread associative array)。可以修改logger设置,让每一行log总是输出MDC内容。如果你的程序使用每线程模式,这可以帮助解决保留上下文的问题。这个java的例子对给定的请求,使用MDC记录每用户的信息:
class UserRequest {
public void execute(int userid) {
MDC.put("user",userid);
// ... all logged message now will display the user=&userid& for this thread context ...
log.info("Successful execution of request");
// user request processing is now finished,no need to log our current user anymore
MDC.remote("user");
123456789101112
class UserRequest {&&&& ...&&&& public void execute(int userid) {&&&&&&&&&&MDC.put("user",userid);&&&&&&&&&&&// ... all logged message now will display the user=&userid& for this thread context ...&&&&&&&&&&log.info("Successful execution of request");&&&&&&&&&&&// user request processing is now finished,no need to log our current user anymore&&&&&&&&&&MDC.remote("user");&&&& }}
提示,MDC系统在异步logging模式中的表现并不好,例如Akka的logging系统。因为MDC是保存在一个每线程存储区域的,而且在异步系统中你无法保证在写入log的线程是有MDC的那一个。在这种情况下,你需要手动地使用每一个log语句来log这些上下文。
7. 你应该用机器可解析的格式来打日志
Log信息对人很友善,但是对机器就惨了。有时人工地读这些log文件并不足够,你需要进行一些自动化过程(例如通过警报和审查)。或是你想集中存储你的log,以进行搜索。
如下,如果你把log的上下文嵌在string中会发生什么:
log.info("User {} plays {} in game {}",userId,card,gameId);
log.info("User {} plays {} in game {}",userId,card,gameId);
这会生成这样的文本:
7:49:37,656[T1]INFOc.d.g.UserRequestUser1334563plays4ofspadesingame
2013-01-1217:49:37,656[T1]INFOc.d.g.UserRequestUser1334563plays4ofspadesingame
现在,如果你想使它可解析,你需要下面这个(未测试过的)正则表达式:
/User(\d+)plays(.+)ingame(\d+)$/
/User(\d+)plays(.+)ingame(\d+)$/
好了,这并不轻松而且容易出错,把它接入到你代码中已有的string参数中。
这个方法怎么样,我相信Jordan Sissel在他的ruby-cabin库中第一次介绍的: 在你的log里面加入机器可解析格式的上下文。我们上述的例子中这样可以使用JSON:
7:49:37,656[T1]INFOc.d.g.UserRequestUserplays{'user':1334563,'card':'4ofspade','game':}
2013-01-1217:49:37,656[T1]INFOc.d.g.UserRequestUserplays{'user':1334563,'card':'4ofspade','game':}
现在你的log分析器可以更容易地写入,更直接地索引,而且你可以释放logstash所有的威力。
8. 日志不宜太多或太少
这听着貌似很愚蠢。log的数量是有一个合适的平衡的。
太多的log会使从中获得有价值的东西变得困难。当人工地浏览这种十分混乱的log,尝试调试产品在早上3点的一个问题可不是一个好事。
太少的log,你可能无法调试问题: 调试就像在拼一个困难的拼图,你需要得到足够的拼块。
不幸的是,这没有魔法般的规则去知道应该log些什么。所以需要严格地遵从第一第二点,程序可以变得很灵活,轻松地增减log的长度(verbosity)。
解决这个问题的一个方法是,在开发过程中尽可能多地进行log(不要被加入用于程序调试的log所迷惑)。当应用程序进入生产过程时,对生成的log进行一次分析,根据所发现的问题增减log语句。尤其是在调试时,在你需要的部分,你可以有更多的上下文或logging,确保在下一个版本中加入这些语句(可以的话,同时解决它来让这个问题在记忆中保持新鲜)。当然,这需要运维人员和开发者之间大量的交流。
这是一个复杂的任务,但是我推荐你重构logging语句,如你重构代码一样多。这样可以在产品的log和它的log语句的修改中有一个紧密的反馈循环。如果你的组织有一个连续的交付进程的话,它会十分有效,正如持续的重构。
Logging语句是与代码注释同级的代码元数据。保持logging语句与代码相同步是很重要的。没什么比调试时获得与所运行的代码毫无关系的信息更糟了。
9. 你应该考虑阅读者
为什么要对应用程序做log
唯一的答案是,在某一天会有人去读它(或是它的意义)。更重要的是,猜猜谁会读它,这是很有趣的事。对于不同的”谁”,你将要写下的log信息的内容,上下文,类别和level会大不同。
这些”谁”包括:
一个尝试自己解决问题的终端用户(想象一个客户端或桌面程序)
一个在调试产品问题的系统管理员或者运维工程师
一个在开发中debug,或者在解决产品问题的开发者
开发者了解程序内部,所以给他的log信息可以比给终端用户的复杂得多。为你的目标阅读者调整你的表达方式,乃至为此加入额外的类别(dedicate separate catagories)。
10. 你不应该只为调试而log
正如log会有不同的阅读者,它也有不同的使用理由。即便调试是最显而易见的阅读log的目的,你同样可以有效地把log用在:
审查: 有时商业上会有需求。这可以获取与管理或者合法用户的有意义的事件。通常会有一些语句描述这个系统中的用户在做些什么(例如谁登录了,谁在编辑……)
建档: log是打上了时间戳的(有时是微妙级的),可以成为一个为程序各部分建档的好工具。例如记录一个操作的开始和结束,你可以自动化(通过解析log)或是在调试中,进行性能度量,而不需要把这些度量加到程序中。
统计: 如果你每次对一个特定事件(例如特定的错误或事件)进行log,你可以对运行中的程序(或用户行为)进行有趣的统计。这可以添加(hook)到一个警报系统中去连续地发现大量error。
我希望这可以帮助你生成更多有用的log。如果我忘记了一些必须的(对你而言)建议,请谅解。对了,如果你看了这篇博客之后并不能更好地进行log,我并不负责 :)
如果这10个建议还不够的话,尽管在评论中补充更多有用的建议。
关于作者:
可能感兴趣的话题
写的好,多阅读几遍。之前对log几乎一点都不了解,看了这篇文章,多少知道了些log的作用和使用方式。收藏下,以后再读,希望以后能多多了解如何使用和编写log。
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2018 伯乐在线

我要回帖

 

随机推荐