`
shijinling87807697
  • 浏览: 49306 次
  • 性别: Icon_minigender_1
  • 来自: 沈阳
社区版块
存档分类
最新评论

异常框架-异常链和log4j

 
阅读更多
异常链
2010-03-09 20:18

 

package test;  

/**
*  
* @author jenhy
*
*/


class HighLevelException extends Exception{


HighLevelException(Exception e){
   super(e);
}
}

class MiddleLevelException extends Exception{

MiddleLevelException(Exception e){
   super(e);
}
}

class LowLevelException extends Exception{

LowLevelException(){
   super();
}

LowLevelException(Exception e){
   super(e);
}
}

public class ExceptionTest {  

    /**
     *  
     * @throws HighLevelException
     */
    public void highLevelAccess() throws HighLevelException {  
        try {  
            middleLevelAccess();  
        } catch (Exception e) {  
            throw new HighLevelException(e);  
        }  
    }  

    /**
     *  
     * @throws MiddleLevelException
     */
    public void middleLevelAccess() throws MiddleLevelException {  
        try {  
            lowLevelAccess();  
        } catch (Exception e) {  
            throw new MiddleLevelException(e);  
        }  
    }  

    /**
     *  
     * @throws LowLevelException
     */
    public void lowLevelAccess() throws LowLevelException {  
        throw new LowLevelException();  
    }  

    /**
     *  
     * @param args
     */
    public static void main(String[] args) {  
        try {  
            new ExceptionTest().highLevelAccess();  
        } catch (HighLevelException e) {  
            Throwable cause = e;  
            for (;;) {  
                if (cause == null)  
                    break;  

                //打印Cause by  
                System.out.println("Caused by: " + cause.getClass().getName() + ":" + cause.getMessage());  

                //打印堆栈  
                StackTraceElement[] ste = cause.getStackTrace();  
                for (int i = 0; i < ste.length; i++) {  
                    System.out.println("ClassName" + i + ":" + ste[i].getClassName() + "\nMethodName:" + ste[i].getMethodName() + "\nFileName:" + ste[i].getMethodName() + "\nLineNumber:" + ste[i].getLineNumber());  
                    System.out.println();  
                }  

                //递归  
                cause = cause.getCause();  
            }  
        }  
    }  

}

 

当程序捕获到了一个底层异常le,在处理部分选择了继续抛出一个更高级别的新异常给此方法的调用者。这样异常的原因就会逐层传递。这样,位于高层的异常递 归调用getCause()方法,就可以遍历各层的异常原因。这就是Java异常链的原理。异常链的实际应用很少,发生异常时候逐层上抛不是个好注意,上 层拿到这些异常又能奈之何?而且异常逐层上抛会消耗大量资源,因为要保存一个完整的异常链信息。

 

实践中的异常框架:

 

package cn.java.exception;

/**
 *
 * @author jenhy
 *
 */
public class ExceptionTest {

 /**
  *
  * @throws Exception
  */
 public void highLevelAccess() throws Exception {
  try {
   middleLevelAccess();
  } catch (Exception e) {
//   throw new HighLevelException(e);
   throw e;
  }
 }

 /**
  *
  * @throws MiddleLevelException
  */
 public void middleLevelAccess() throws Exception {
  try {
   lowLevelAccess();
  } catch (Exception e) {
//   throw new MiddleLevelException(e);
   throw e;
  }
 }

 /**
  *
  * @throws LowLevelException
  */
 public void lowLevelAccess() throws Exception {
//  throw new LowLevelException();
  throw new RuntimeException();
 }

 /**
  *
  * @param args
  */
 public static void main(String[] args) {
  try {
   new ExceptionTest().highLevelAccess();
  } catch (Exception e) {
   Throwable cause = e;
   for (;;) {
    if (cause == null)
     break;

    //打印Cause by
    System.out.println("Caused by: " + cause.getClass().getName() + ":" + cause.getMessage());

    //打印堆栈
    StackTraceElement[] ste = cause.getStackTrace();
    for (int i = 0; i < ste.length; i++) {
     System.out.println("ClassName" + i + ":" + ste[i].getClassName() + "/nMethodName:" + ste[i].getMethodName() + "/nFileName:" + ste[i].getMethodName() + "/nLineNumber:" + ste[i].getLineNumber());
     System.out.println();
    }

    //递归
    cause = cause.getCause();
   }
  }
 }

}

这样可以不用一层套一层的使用自定义的异常类,可以直接把异常信息通过log4j放入到文件中。

log4j 详解:


Log4J的配置文件(Configuration File)就是用来设置记录器的级别、存放器和布局的,它可接key=value格式的设置或xml格式的设置信息。通过配置,可以创建出Log4J的运行环境。

1. 配置文件

 [level] 是日志输出级别,共有5级:

FATAL       0  
ERROR     
3
 
WARN      
4
 
INFO      
   6
 
DEBUG     
7


Appender

org.apache.log4j.ConsoleAppender(控制台),
org.apache.log4j.FileAppender(文件),
org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件),
org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件),
org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)

 

 

org.apache.log4j.HTMLLayout(以HTML表格形式布局),
org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)


打印参数: Log4J采用类似C语言中的printf函数的打印格式格式化日志信息,如下:

   %m   输出代码中指定的消息
  %p   输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL 
  %r   输出自应用启动到输出该log信息耗费的毫秒数 
  %c   输出所属的类目,通常就是所在类的全名 
  %t   输出产生该日志事件的线程名 
  %n   输出一个回车换行符,Windows平台为“/r/n”,Unix平台为“/n” 
  %d   输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss
, SSS},输出类似:2002年10月18日  22 10 28 921
 
  %l   输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:
10



2. 为不同的 Appender 设置日志输出级别:
当调试系统时,我们往往注意的只是异常级别的日志输出,但是通常所有级别的输出都是放在一个文件里的,如果日志输出的级别是BUG!?那就慢慢去找吧。
这时我们也许会想要是能把异常信息单独输出到一个文件里该多好啊。当然可以,Log4j已经提供了这样的功能,我们只需要在配置中修改AppenderThreshold
就能实现,比如下面的例子:

[配置文件]

[代码中使用]

### set log levels ###
log4j.rootLogger
= debug ,  stdout ,  D ,
 E

### 输出到控制台 ###
log4j.appender.stdout
=
org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target
=
System.out
log4j.appender.stdout.layout
=
org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern
=  %d{ABSOLUTE} %5p %c{ 1
}:%L - %m%n

### 输出到日志文件 ###
log4j.appender.D
=
org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File
=
logs/log.log
log4j.appender.D.Append
=
true
log4j.appender.D.Threshold
=
DEBUG ## 输出DEBUG级别以上的日志
log4j.appender.D.layout
=
org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern
= %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]
 %m%n

### 保存异常信息到单独文件 ###
log4j.appender.D
=
org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File
=
logs/error.log ## 异常日志文件名
log4j.appender.D.Append
=
true
log4j.appender.D.Threshold
=
ERROR ## 只输出ERROR级别以上的日志!!!
log4j.appender.D.layout
=
org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern
= %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

 

 

public   class  TestLog4j  {
    
public   static   void  main(String[] args) 
{
        PropertyConfigurator.configure(
" D:/Code/conf/log4j.properties "
);
        Logger logger 
=  Logger.getLogger(TestLog4j. class
);
        logger.debug(
" debug "
);
        logger.error(
" error "
);
    }

}


运行一下,看看异常信息是不是保存在了一个单独的文件error.log中

高级使用
实验目的:
 1.把FATAL级错误写入2000NT日志
 2. WARN,ERROR,FATAL级错误发送email通知管理员
 3.其他级别的错误直接在后台输出
实验步骤:
 输出到2000NT日志
 1.把Log4j压缩包里的NTEventLogAppender.dll拷到WINNT/SYSTEM32目录下
 2.写配置文件log4j.properties
# 在2000系统日志输出
 log4j.logger.NTlog=FATAL, A8
 # APPENDER A8
 log4j.appender.A8=org.apache.log4j.nt.NTEventLogAppender
 log4j.appender.A8.Source=JavaTest
 log4j.appender.A8.layout=org.apache.log4j.PatternLayout
 log4j.appender.A8.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n
3.调用代码:
 Logger logger2 = Logger.getLogger("NTlog"); //要和配置文件中设置的名字相同
 logger2.debug("debug!!!");
 logger2.info("info!!!");
 logger2.warn("warn!!!");
 logger2.error("error!!!");
 //只有这个错误才会写入2000日志
 logger2.fatal("fatal!!!");
发送email通知管理员:
 1. 首先下载JavaMail和JAF,
 
http://java.sun.com/j2ee/ja/javamail/index.html
  http://java.sun.com/beans/glasgow/jaf.html
 在项目中引用mail.jar和activation.jar。
 2. 写配置文件
 # 将日志发送到email
 log4j.logger.MailLog=WARN,A5
 #  APPENDER A5
 log4j.appender.A5=org.apache.log4j.net.SMTPAppender
 log4j.appender.A5.BufferSize=5
 
log4j.appender.A5.To=chunjie@yeqiangwei.com
 log4j.appender.A5.From=error@yeqiangwei.com
 log4j.appender.A5.Subject=ErrorLog
 log4j.appender.A5.SMTPHost=smtp.263.net
 log4j.appender.A5.layout=org.apache.log4j.PatternLayout
 log4j.appender.A5.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n
 3.调用代码:
 //把日志发送到mail
 Logger logger3 = Logger.getLogger("MailLog");
 logger3.warn("warn!!!");
 logger3.error("error!!!");
 logger3.fatal("fatal!!!");
在后台输出所有类别的错误:
 1. 写配置文件
 # 在后台输出
 log4j.logger.console=DEBUG, A1
 # APPENDER A1
 log4j.appender.A1=org.apache.log4j.ConsoleAppender
 log4j.appender.A1.layout=org.apache.log4j.PatternLayout
 log4j.appender.A1.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n
 2.调用代码
 Logger logger1 = Logger.getLogger("console");
 logger1.debug("debug!!!");
 logger1.info("info!!!");
 logger1.warn("warn!!!");
 logger1.error("error!!!");
 logger1.fatal("fatal!!!");
--------------------------------------------------------------------
 全部配置文件:log4j.properties
 # 在后台输出
 log4j.logger.console=DEBUG, A1
 # APPENDER A1
 log4j.appender.A1=org.apache.log4j.ConsoleAppender
 log4j.appender.A1.layout=org.apache.log4j.PatternLayout
 log4j.appender.A1.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n
# 在2000系统日志输出
 log4j.logger.NTlog=FATAL, A8
 # APPENDER A8
 log4j.appender.A8=org.apache.log4j.nt.NTEventLogAppender
 log4j.appender.A8.Source=JavaTest
 log4j.appender.A8.layout=org.apache.log4j.PatternLayout
 log4j.appender.A8.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n
# 将日志发送到email
 log4j.logger.MailLog=WARN,A5
 #  APPENDER A5
 log4j.appender.A5=org.apache.log4j.net.SMTPAppender
 log4j.appender.A5.BufferSize=5
 
log4j.appender.A5.To=chunjie@yeqiangwei.com
 log4j.appender.A5.From=error@yeqiangwei.com
 log4j.appender.A5.Subject=ErrorLog
 log4j.appender.A5.SMTPHost=smtp.263.net
 log4j.appender.A5.layout=org.apache.log4j.PatternLayout
 log4j.appender.A5.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n
全部代码:Log4jTest.java
 
/*
  * 创建日期 2003-11-13
  */
 package edu.bcu.Bean;
 import org.apache.log4j.*;
 //import org.apache.log4j.nt.*;
 //import org.apache.log4j.net.*;
 /**
  * @author yanxu
  */
 public class Log4jTest
 {
  public static void main(String args[])
  {
   PropertyConfigurator.configure("log4j.properties");
   //在后台输出
   Logger logger1 = Logger.getLogger("console");
   logger1.debug("debug!!!");
   logger1.info("info!!!");
   logger1.warn("warn!!!");
   logger1.error("error!!!");
   logger1.fatal("fatal!!!");
//在NT系统日志输出
   Logger logger2 = Logger.getLogger("NTlog");
   //NTEventLogAppender nla = new NTEventLogAppender();
   logger2.debug("debug!!!");
   logger2.info("info!!!");
   logger2.warn("warn!!!");
   logger2.error("error!!!");
   //只有这个错误才会写入2000日志
   logger2.fatal("fatal!!!");
//把日志发送到mail
   Logger logger3 = Logger.getLogger("MailLog");
   //SMTPAppender sa = new SMTPAppender();
   logger3.warn("warn!!!");
   logger3.error("error!!!");
   logger3.fatal("fatal!!!");
  }
 }

 

 

使用log4j来实践error和info输出到不同的文件中:

Log4jConfigTest.java

 

package logging;

import org.apache.log4j.Logger;


public class Log4jConfigTest {

 public static final Logger logger=Logger.getLogger(Log4jConfigTest.class);

 public void highLevelAccess() throws Exception {
  try {
   middleLevelAccess();
  } catch (Exception e) {
   throw e;
  }
 }
  
 public void middleLevelAccess() throws Exception {
  try {
   lowLevelAccess();
  } catch (Exception e) {
   throw e;
  }
 }
 
 public void lowLevelAccess() throws Exception {
  throw new RuntimeException();
 }

 public static void main(String[] args) {
   Log4jConfigTest lc=null;
  try{
   lc=new Log4jConfigTest();
   lc.highLevelAccess();
  } catch (Exception e) {
   //两个处理,为用户:提示错误信息,为程序员:提供错误具体位置。
   Throwable cause = e;
   for (;;) {
    if (cause == null)
     break;
    LogFactory.infoLogger.info("Caused by: " + cause.getClass().getName() + ":" + cause.getMessage());
    LogFactory.errorLogger.error("Caused by: " + cause.getClass().getName() + ":" + cause.getMessage());
    //打印堆栈
    StackTraceElement[] ste = cause.getStackTrace();
    for (int i = 0; i < ste.length; i++) {
     LogFactory.infoLogger.info("异常链" + i + ":" + ste[i].getClassName() + "/nMethodName:" + ste[i].getMethodName() +"/nLineNumber:" + ste[i].getLineNumber());
     LogFactory.errorLogger.error("异常链" + i + ":" + ste[i].getClassName() + "/nMethodName:" + ste[i].getMethodName() +"/nLineNumber:" + ste[i].getLineNumber());
    }
    //递归
    cause = cause.getCause();
   }
  }
  }
}

 

 

LogFactory.java

package logging;

import java.io.File;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.SimpleLayout;

public class LogFactory { //设定两个Log

      public static Logger infoLogger = Logger.getLogger("info.logger");

      public static Logger errorLogger = Logger.getLogger("error.logger");

      public static final String PROFILE = "log4j.properties";

     //设定异常log输出的路径

       private static final String PATH = "D:\\";

          static{

                  try{

                      URL configFileResource = (new File(LogFactory.class.getResource("/").getPath()+PROFILE)).toURL();          

                      PropertyConfigurator.configure(configFileResource); 

                }catch(Exception e){

                     e.printStackTrace();

           }

         }

      public LogFactory(){

             try {

                         Date date = new Date();

                        SimpleDateFormat sf = new SimpleDateFormat("yyyyMMddHHmmss");

                        String fileName = PATH + "exception_" + sf.format(date).toString() + ".log";

                            FileAppender exceptionAppender = new FileAppender(new SimpleLayout(), fileName);      

                         errorLogger.addAppender(exceptionAppender);

                      } catch (Exception e) {

                              e.printStackTrace();

                     }

                   }

         }

Log4j.properties

 

log4j.category.info.logger=INFO,info

log4j.category.error.logger=ERROR,error

log4j.appender.info = org.apache.log4j.FileAppender

log4j.appender.info.File=D:\\logs\\loginfo.log

log4j.appender.info.MaxFileSize=100kb

log4j.appender.info.MaxBackupIndex=4

log4j.appender.info.layout=org.apache.log4j.PatternLayout

log4j.appender.info.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss.SSS} %-5p %m%n log4j.appender.error=org.apache.log4j.RollingFileAppender

log4j.appender.error = org.apache.log4j.FileAppender

log4j.appender.error.File=D:\\logs\\error.log

log4j.appender.error.MaxFileSize=100kb

log4j.appender.error.MaxBackupIndex=4

log4j.appender.error.layout=org.apache.log4j.PatternLayout

log4j.appender.error.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss.SSS} %-5p %m%n

 

分享到:
评论

相关推荐

    2014-09-18-AOP_LOG4J

    写这个日志的框架的主要想法是,先前在开发的时候logger都是到处都有不便于管理,于是想到了利用AOP加注解的方式统一管理,异常主要分为业务异常和系统异常, 业务异常应该由程序员在写业务的时候在可能发生业务异常...

    ssh2+log4j+异常简单框架

    struts2 hibernate spring log4j 框架的简单列子 jar.jpg为对应jar包。lib下jar包已去除

    Spring_demo:spring_demo,使用log4j2,异常框架,maven

    spring_demo spring_demo,使用log4j2,异常框架,maven

    SSH 框架所需JAR包

    2.commons-logging-1.1.1.jar(ASF出品的日志包,struts2 2、spring、hibernate框架使用这个日志包来支持Log4J和JDK 1.4+的日志记录) 3.common-annotations.jar(支持注解的包) 4.aspectjrt.jar(支持AOP的包) 5....

    atools:atools是一个扩展了Qt的静态库,用于异常处理,类似于log4j的日志记录框架,与Flight Simulator相关的实用程序(例如BGL阅读器)等

    atools:atools是一个扩展了Qt的静态库,用于异常处理,类似于log4j的日志记录框架,与Flight Simulator相关的实用程序(例如BGL阅读器)等

    国大科技入聘人员资料-JAVA通用快速开发框架源码

    JAVA通用快速开发框架源码 通用快速开发框架是一套轻量级的权限系统,主要包括用户管理、角色管理、部门管理、菜单管理、...· 日志管理:SLF4J 1.7、Log4j · 单元测试:JUnit 4.12 · API接口文档:Swagger 2.7

    Oramake Framework:Oracle数据库中的开发框架-开源

    -日志记录功能(类似于log4j,将PL / SQL异常消息长度的限制扩展到32K); -易于处理文件; -灵活定制的调度程序(类似于cron); -创建Microsoft Excel xml文件; -发送和接收电子邮件(smtp和pop3协议的实现); ...

    MF00653-JAVA通用快速开发框架源码.zip

    JAVA通用快速开发框架源码 注意:不带技术支持,有帮助文件,虚拟商品...· 日志管理:SLF4J 1.7、Log4j · 单元测试:JUnit 4.12 · API接口文档:Swagger 2.7.0 · 页面交互:Vue 2.x + Bootstrap+ HTML5 + CSS3

    springboot学习

    chapter4-2-3:对log4j进行多环境不同日志级别的控制 chapter4-2-4:使用AOP统一处理Web请求日志 chapter4-2-5:使用log4j记录日志到MongoDB chapter4-2-6:Spring Boot 1.5.x新特性:动态修改日志级别] 安全管理 ...

    互联网创意产品众筹平台

    问题一箩筐-关于打印日志log4j问题5 y: }- e: Z$ p6 X9 d0 A9 @ │ 7.问题一箩筐-生产环境模拟, y; v4 Z% p0 }& I+ X* B) t# j │ 8.问题一箩筐-相对路径和绝对路径 │ 9.问题一箩筐-自定义监听器,解决上下文路径...

    LearnHow2J:练习How2J的代码

    JavaUse ------ :开发中常用的工具包,包括Log4j,测试Junit等。 网站------:前端内容,包括前端基础内容和流行框架。 JavaEE ------ :JavaEE基础内容,包括Tomcat,JSP等。 JavaFramework ------ :Java框架...

    spring boot 全面的样例代码

    - chapter4-2-2:[使用log4j记录日志](http://blog.didispace.com/springbootlog4j/) - chapter4-2-3:[对log4j进行多环境不同日志级别的控制](http://blog.didispace.com/springbootlog4jmuilt/) - chapter4-2-4:...

    web项目常用jar包及说明.zip

    2.commons-logging-1.1.1.jar(ASF出品的日志包,struts2 2、spring、hibernate框架使用这个日志包来支持Log4J和JDK 1.4+的日志记录) 3.common-annotations.jar(支持注解的包) 4.aspectjrt.jar(支持AOP的包) 5....

    java项目框架

    该代码采用ssh框架模式开发,页面控制跳转交给struts处理,数据库持久化用hibernate实现,事务和Dao、Service的管理交给spring,日志实现采用log4j,代码还包含了异常处理等。可以在此基础上稍作修改,从而在项目中...

    IOIF面向项目的开源开发框架

    IOIF以EXTJS为前端,以Spring、Struts、Hibernate为后端,整合了Proxool、Log4j、Quartz、Oscache、Castor、Memcached、redis等优秀的开源软件。 支持Tomcat6及Resin3等应用服务器,支持Oracle、MYSQL等数据库。IOIF...

    SSH 项目 整合jar包

    2.commons-logging-1.1.1.jar(ASF出品的日志包,struts2 2、spring、hibernate框架使用这个日志包来支持Log4J和JDK 1.4+的日志记录) 3.common-annotations.jar(支持注解的包) 4.aspectjrt.jar(支持AOP的包) 5....

    IOIF基于开源技术的JAVA开发框架

    IOIF以EXTJS为前端,以Spring、Struts、Hibernate为后端,整合了Proxool、Log4j、Quartz、Oscache、Castor、Memcached、redis等优秀的开源软件。 支持Tomcat6及Resin3等应用服务器,支持Oracle、MYSQL等数据库。IOIF...

    spring-boot-seed:SpringBoot骨架项目,集成SpringBoot、Mybatis、Druid、Mapper、PageHelper、Redis、Shiro、Swagger2、Log4j2等技术

    spring-boot-seed项目介绍SpringBoot的种子框架项目,个人学习使用,集成一些常用的框架功能,方便快速开发。软件架构spring-boot-seed ├── src/main/java/com.dazzlzy | ├── common -- 通用代码包 | | ├─...

    StudentInfoManage2.rar

    在Java编程中,信息管理通常涉及数据的存储、处理和系统的组织。以下是一些Java中信息管理的常见技术和方法: 1. **数据结构和集合类...6. **日志记录**:Java中流行的日志框架(如Log4j、Logback)可以帮助记录程序运

    xmljava系统源码-superman:java脚手架工程,目前该脚手架工程已整合了swagger2、mybatisplus、log4j2,

    log4j日志的配置 swagger2整合 MybatisPlus 代码生成器 分页 逻辑删除 TODO 工具类的整合 常用中间件的封装 Quick Start【快速使用】 1.下载源码 git clone https://github.com/TiantianUpup/superman.git 2.打开...

Global site tag (gtag.js) - Google Analytics