查看: 916|回复: 0

[手机开发] 程序员你为什么这么累? - 日志规范

发表于 2018-5-9 08:00:03

导读:程序员你为什么这么累?

接口定义规范 controller规范 日志规范 异常处理规范 国际化和参数校验规范 工具类规范 函数编写建议 配置建议

开发中日志这个问题,每个公司都强调,也制定了一大堆规范,但根据实际情况看,效果不是很明显,主要是这个东西不好测试和考核,没有日志功能一样跑啊。

但编程活久见,开发久了,总会遇到“这个问题生产环境上能重现,但是没有日志,业务很复杂,不知道哪一步出错了?” 这个时候,怎么办? 还能怎么办,发个版本,就是把所有地方加上日志,没有任何新功能,然后在让用户重现一遍,拿下日志来看,哦,原来是这个问题。

有没有很熟悉的感觉?

还有一种情况,我们系统有3*5=15个节点,出了问题找日志真是痛苦,一个一个机器翻,N分钟后终于找到了,找到了后发现好多相似日志,一个一个排查;日志有了,发现逻辑很复杂,不知道走到那个分支,只能根据逻辑分析,半天过去了,终于找到了原因。。。一个问题定位就过去了2个小时,变更时间过去了一半。。。

日志要求

所以我对日志的最少有以下2点要求:

1 能找到那个机器

2 能找到用户做了什么

配置nginx

针对第一点,我修改了一下nginx的配置文件,让返回头里面返回是那个机器处理的。

nginx的基本配置,大家查阅一下资料就知道。简单配置如下(生产环境比这个完善)

nginx配置

效果如图,返回了处理的节点:

效果图

第二点,要知道用户做了什么。用户信息是很重要的一个信息,能帮助海量日志里面能快速找到目标日志。一开始要求开发人员打印的时候带上用户,但是发现这个落地不容易,开发人员打印日志都经常忘记,更加不用说日志上加上用户信息,我也不可能天天看代码。所以找了一下log4j的配置,果然log4j有个叫MDC(Mapped Diagnostic Context)的类(技术上使用了ThreadLocal实现,重点技术)。具体使用方法请自行查询。具体使用如下:

UserFilter

filter中得到用户信息,并放入MDC,记住filter后要清理掉(因为tomcat线程池线程重用的原因)。

  1. /**
  2. * 用户信息相关的filter
  3. *
  4. * @author 晓风轻 https://xwjie.github.io/PLMCodeTemplate/
  5. *
  6. */
  7. public class UserFilter implements Filter {
  8. @Override
  9. public void init(FilterConfig filterConfig) throws ServletException {
  10. }
  11. @Override
  12. public void doFilter(ServletRequest request, ServletResponse response,
  13. FilterChain chain) throws IOException, ServletException {
  14. // 得到用户个人相关的信息(登陆的用户,用户的语言)
  15. fillUserInfo((HttpServletRequest) request);
  16. try {
  17. chain.doFilter(request, response);
  18. } finally {
  19. // 由于tomcat线程重用,记得清空
  20. clearAllUserInfo();
  21. }
  22. }
  23. private void clearAllUserInfo() {
  24. UserUtil.clearAllUserInfo();
  25. }
  26. private void fillUserInfo(HttpServletRequest request) {
  27. // 用户信息
  28. String user = getUserFromSession(request);
  29. if (user != null) {
  30. UserUtil.setUser(user);
  31. }
  32. // 语言信息
  33. String locale = getLocaleFromCookies(request);
  34. // 放入到threadlocal,同一个线程任何地方都可以拿出来
  35. if (locale != null) {
  36. UserUtil.setLocale(locale);
  37. }
  38. }
  39. private String getLocaleFromCookies(HttpServletRequest request) {
  40. Cookie[] cookies = request.getCookies();
  41. if (cookies == null) {
  42. return null;
  43. }
  44. for (int i = 0; i < cookies.length; i++) {
  45. if (UserUtil.KEY_LANG.equals(cookies[i].getName())) {
  46. return cookies[i].getValue();
  47. }
  48. }
  49. return null;
  50. }
  51. private String getUserFromSession(HttpServletRequest request) {
  52. HttpSession session = request.getSession(false);
  53. if (session == null) {
  54. return null;
  55. }
  56. // 从session中获取用户信息放到工具类中
  57. return (String) session.getAttribute(UserUtil.KEY_USER);
  58. }
  59. @Override
  60. public void destroy() {
  61. }
  62. }
复制代码
用户工具类

用户信息放入MDC:

  1. /**
  2. * 用户工具类
  3. *
  4. * @author 晓风轻 https://xwjie.github.io/PLMCodeTemplate/
  5. *
  6. */
  7. public class UserUtil {
  8. private final static ThreadLocal<String> tlUser = new ThreadLocal<String>();
  9. private final static ThreadLocal<Locale> tlLocale = new ThreadLocal<Locale>() {
  10. protected Locale initialValue() {
  11. // 语言的默认值
  12. return Locale.CHINESE;
  13. };
  14. };
  15. public static final String KEY_LANG = "lang";
  16. public static final String KEY_USER = "user";
  17. public static void setUser(String userid) {
  18. tlUser.set(userid);
  19. // 把用户信息放到log4j
  20. MDC.put(KEY_USER, userid);
  21. }
  22. /**
  23. * 如果没有登录,返回null
  24. *
  25. * @return
  26. */
  27. public static String getUserIfLogin() {
  28. return tlUser.get();
  29. }
  30. /**
  31. * 如果没有登录会抛出异常
  32. *
  33. * @return
  34. */
  35. public static String getUser() {
  36. String user = tlUser.get();
  37. if (user == null) {
  38. throw new UnloginException();
  39. }
  40. return user;
  41. }
  42. public static void setLocale(String locale) {
  43. setLocale(new Locale(locale));
  44. }
  45. public static void setLocale(Locale locale) {
  46. tlLocale.set(locale);
  47. }
  48. public static Locale getLocale() {
  49. return tlLocale.get();
  50. }
  51. public static void clearAllUserInfo() {
  52. tlUser.remove();
  53. tlLocale.remove();
  54. MDC.remove(KEY_USER);
  55. }
  56. }
复制代码
log4j配置

增加用户信息变量,%X{user}

  1. <!-- Appenders -->
  2. <appender name="console" class="org.apache.log4j.ConsoleAppender">
  3. <param name="Target" value="System.out" />
  4. <layout class="org.apache.log4j.PatternLayout">
  5. <param name="ConversionPattern"
  6. value="[%t]%-d{MM-dd HH:mm:ss,SSS} %-5p:%X{user} - %c - %m%n" />
  7. </layout>
  8. </appender>
复制代码
日志要求

我做好上面2步后,对开发人员的日志只有3点要求:

修改(包括新增)操作必须打印日志

大部分问题都是修改导致的。数据修改必须有据可查。

条件分支必须打印条件值,重要参数必须打印

尤其是分支条件的参数,打印后就不用分析和猜测走那个分支了,很重要!如下面代码里面的userType,一定要打印值,因为他决定了代码走那个分支

log

数据量大的时候需要打印数据量

前后打印日志和最后的数据量,主要用于分析性能,能从日志中知道查询了多少数据用了多久。这点是建议。自己视情况而决定是否打印,我一般建议打印。

日志效果图

加上 我的编码习惯 - Controller规范 这篇文章的AOP,最后的日志如下:

log

其实日志的级别我到不是很关注,还没有到关注这步到时候。开发组长需要做好后勤工作(前面2步),然后制定简单规则,规则太多太能落实了。

新手建议

日志这个东西,更多是靠自觉,项目组这么多人,我也不可能一个一个给大家看代码,然后叫你加日志。我分析了一下,为什么有些人没有打印日志的习惯,说了多次都改不过来。我建议大家养成下面的习惯,这样你的日志就会改善多了!

不要依赖debug,多依赖日志。

别人面对对象编程,你面对debug编程。有些人无论什么语言,最后都变成了面对debug编程。哈哈。这个习惯非常非常不好!debug会让你写代码的时候偷懒不打日志,而且很浪费时间。改掉这个恶习。

代码开发测试完成之后不要急着提交,先跑一遍看看日志是否看得懂。

日志是给人看的,只要热爱编程的人才能成为合格程序员,不要匆匆忙忙写完功能测试ok就提交代码,日志也是功能的一部分。要有精益求精的工匠精神!

=============GITHUB地址==============

所有的代码细节都在已经上了github了,地址 xwjie/PLMCodeTemplate,欢迎加星。有问题欢迎提出。

觉得有用请点赞加关注,加星,fork,:)



回复

使用道具 举报