【Logback技术专题】「入门到精通系列教程」深入探索Logback日志框架的原理分析和开发实战技术指南(下篇)

2023-12-15 14:37:27

根节点configuration包含的属性

在这里插入图片描述

基本参数详解

  • scan属性设置为true时,配置文件有改变时将会被重新加载,默认为true
  • scanPeriod设置监测配置文件是否有修改的时间间隔。如果未指定时间单位,将默认为毫秒。只有当scantrue时才生效。默认间隔为1分钟。
  • debug属性设置为true时将会打印出logback的内部日志信息,方便实时查看logback运行状态。默认为false
 <configuration scan="true" scanPeriod="60 seconds" debug="false">  
     <!-- 其他配置省略-->  
 </configuration>  

子节点介绍

设置上下文名称<contextName>

每个logger都默认关联到logger上下文,上下文名称默认为“default”。但是可以通过设置来将其设置为其他名称,以便于区分不同应用程序的记录。一旦设置,就无法修改。

使用案例
  <configuration scan="true" scanPeriod="60 seconds" debug="false">  
       <contextName>myApplication</contextName>  
       <!-- 其他配置省略-->  
  </configuration>  

设置变量属性值<property>

使用标签定义变量值,该标签有两个属性,分别是name和value。name表示变量名,value则是变量所需的值,在通过该标签定义的变量会被插入到logger上下文中。定义好变量后,可以在后续的程序中使用“${}”来引用这些变量。

使用<property>标签定义一个名属性,并在<contextName>标签中使用该属性的值,即可为logger设置上下文名称。

<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <property name="APP_Name" value="myAppName" />
    <contextName>${APP_Name}</contextName>
    <!-- 其他配置省略-->
</configuration>

举个例子,使用标签定义一个用于设置logger上下文的名称,然后在标签中使用该名称即可。

获取时间戳字符串<timestamp>

在标签内,使用属性来定义一个名称,为项目使用当前日期时间命名,设置属性来定义日期时间格式。示例代码如下:

<timestamp key="name" datePattern="yyyyMMddHHmm">

其中,key属性指定用于identifier的名称;datePattern属性定义将当前时间转换为字符串的模式。

您可以根据自己项目的需要修改日期时间格式。

例如将解析配置文件的时间作为上下文名称:

     <configuration scan="true" scanPeriod="60 seconds" debug="false">  
          <timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>   
          <contextName>${bySecond}</contextName>  
          <!-- 其他配置省略-->  
    </configuration>   

设置logger

在Java应用程序的日志配置中,使用<logger>元素来设置某个包或具体的类的日志打印级别,并且可以指定日志输出的目的地(<appender>)。
在这里插入图片描述

  • <logger>元素只有一个必须的属性 name,用于指定要设置日志级别的包名或类名,可以包含通配符。
  • <logger>元素还可以包含可选的 level 属性,用于指定日志输出的级别,包括 TRACE、DEBUG、INFO、WARN、ERROR 和 OFF。
  • <logger>元素还可以包含可选的 additivity 属性,用于指定是否将日志事件发送给该Logger的父级Logger。

可以在<logger>中添加多个<appender-ref>元素来指定用于添加到该logger的各个appender

<logger name="com.campus.o2o" level="${log.level}" additivity="true">
    <appender-ref ref="debugAppender" />
    <appender-ref ref="infoAppender" />
    <appender-ref ref="errorAppender" />
</logger>
 <!-- 特殊的logger,根logger -->
 <root lever="info">
    <!-- 指定默认的日志输出 -->
    <appender-ref ref="consoleAppender" />
 </root>
root根节点

根logger是一个<logger>元素,它只有一个level属性,应为已经被命名为"root"。level属性用来设置打印级别,大小写无关,可选的级别有:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能设置为INHERITED或者同义词NULL。它的默认级别是DEBUG。

 
 <!-- 特殊的logger,根logger -->
 <root lever="info">
     <!-- 指定默认的日志输出 -->
     <appender-ref ref="consoleAppender" />
 </root>

<root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger。

additivity的继承传递模式

appender配置表示将日志打印到控制台。<logger name="logback" />控制logback包下所有类的日志打印,未设置打印级别,继承自上级的DEBUG级别。additivity未设置,默认为true,将此logger的打印信息向上传递。未设置appender,此logger本身不打印任何信息。

<root level="DEBUG">将root的打印级别设置为DEBUG并指定名为"STDOUT"的appender。

常用配置详解 <appender>

<appender><configuration>的子节点,用于写日志的组件。

在这里插入图片描述
<appender>必须包含两个属性:name和class。name用于指定appender的名称,class用于指定appender的全限定名。

在这里插入图片描述

ConsoleAppender

要将日志输出到控制台,您可以使用以下子节点:

  • <encoder>:用于对日志进行格式化。

  • <target>:使用字符串 “System.out” 或 “System.err”,默认为 “System.out”。

例如:
     <configuration>  
      <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">  
        <encoder>  
          <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>  
        </encoder>  
      </appender>     
      <root level="DEBUG">  
        <appender-ref ref="STDOUT" />  
      </root>  
    </configuration>
FileAppender

将日志写入文件时,可以使用以下子节点进行配置:

  • <file>: 指定要写入的文件名,可以是相对或绝对路径。如果上级目录不存在,系统会自动创建。如果没有指定,默认为空。(提示:可以根据需要指定文件名和路径)
  • <append>: 如果设置为 true,则日志会追加到文件末尾;如果设置为 false,则会清空现有文件内容。默认为 true。(提示:可以选择是否追加日志或覆盖文件内容)
  • <encoder>: 用于对记录的事件进行格式化的编码器参数。(提示:可以根据需要设置适当的编码器参数)
  • <prudent>: 如果设置为 true,即使其他 FileAppender 正在向该文件写入,日志也会被安全写入文件,但效率会较低。默认为 false。 (提示:可以选择是否启用安全写入模式)
例如:
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>/path/to/testFile.log</file>
    <append>true</append>
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender>
  <root level="DEBUG">
    <appender-ref ref="FILE"/>
  </root>
</configuration>
RollingFileAppender

在这里插入图片描述
这个配置使用RollingFileAppender,满足以下要求:

  • <file>:指定主日志文件的路径,这里使用/path/to/logFile.log作为示例,请替换为你实际需要的路径。
  • <append>:设置为true,表示日志会追加到文件的结尾。
  • <encoder>:使用给定的模式对日志事件进行格式化。
  • <rollingPolicy>:使用TimeBasedRollingPolicy进行滚动记录,这里的fileNamePattern用于指定滚动后生成的文件名模式。
  • <triggeringPolicy>:使用SizeBasedTriggeringPolicy设置日志文件的最大大小为10MB,当日志文件大小达到上限时会触发滚动记录。
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>/path/to/logFile.log</file>
    <append>true</append>
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender>
  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
</configuration>

请确保将/path/to/logFile.log/path/to/logFile-%d{yyyy-MM-dd}.log替换为适合你的实际文件路径和命名模式。如有需要,请进行其他配置调整。如果你有任何其他问题,请随时向我提问。

RollingFileAppender的file节点是可选项,它用于设置活动文件和归档文件的位置。如果通过设置file节点,可以将当前日志记录到指定的活动文件,而活动文件的名称不会改变。如果未设置file节点,活动文件的名称将根据fileNamePattern的值,每隔一段时间变化一次。目录分隔符可使用"/“或”"来表示。

rollingPolicy

在这里插入图片描述

fileNamePattern

该内容包含文件名及"%d"转换符,其中"%d"可以包含java.text.SimpleDateFormat指定的时间格式,例如:%d{yyyy-MM}。如果未直接使用"%d",则默认的时间格式为yyyy-MM-dd。

<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>/path/to/logFile-%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
maxHistory

可选节点用于控制归档文件的最大保留数量。如果设置每月滚动,并且设置<maxHistory>为6,那么只会保留最近的6个月的文件,并删除之前的旧文件。需要注意的是,删除旧文件时,与归档相关的目录也会被删除。

配置案例
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>/path/to/logFile-%d{yyyy-MM-dd}.log</fileNamePattern>
    <maxHistory>30</maxHistory>    
</rollingPolicy>
FixedWindowRollingPolicy

该策略基于固定窗口算法对文件进行重命名,具有以下子节点:

  • <minIndex>: 窗口索引的最小值。
  • <maxIndex>: 窗口索引的最大值。如果用户指定的窗口大小超过该值,系统会自动将窗口大小设置为12。
  • <fileNamePattern>: 文件名的模式。

命名模式必须包含 “%i”,例如,假设最小值和最大值分别为 1 和 2,文件名模式可以是 “mylog%i.log”。这样将会生成归档文件 “mylog1.log” 和 “mylog2.log”。另外,您还可以选择文件的压缩选项,例如 “mylog%i.log.gz” 或 “mylog%i.log.zip”。

   
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
   <maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
    
triggeringPolicy

SizeBasedTriggeringPolicy:该策略会监视当前活动文件的大小,如果其大小超过指定值,将触发 RollingFileAppender 执行文件滚动操作。此策略只适用于单一节点的情况。

<maxFileSize>:这是指定的活动文件大小,默认值为10MB。

其他的Appender
  • SocketAppender:SocketAppender用于将日志消息通过网络传输到远程的Socket服务器。它可以将日志发送到指定的远程Socket地址,并将日志消息传递给服务器进行处理和存储。

  • SMTPAppender:SMTPAppender用于通过邮件将日志消息发送给指定的收件人。它可以将日志消息作为邮件附件发送,并支持邮件服务器的配置,如SMTP服务器地址、认证信息等。

  • DBAppender:DBAppender用于将日志消息保存到数据库中。它可以将日志消息插入到指定的数据库表中,并支持各种数据库连接和配置选项。

  • SyslogAppender:SyslogAppender用于将日志消息发送到Syslog服务器。它可以将日志消息转发到指定的Syslog服务器,并支持Syslog协议的各种配置选项。

  • SiftingAppender:SiftingAppender用于根据不同的条件将日志消息路由到不同的目标Appender。它可以根据指定的条件(如日志级别、日志标签等)将日志消息发送到不同的Appender进行处理和存储。

案例分析
每天生成一个日志文件,保存30天的日志文件
     <configuration>   
      <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">   
          
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">   
          <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>   
          <maxHistory>30</maxHistory>    
        </rollingPolicy>   
       
        <encoder>   
          <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>   
        </encoder>   
      </appender>    
       
      <root level="DEBUG">   
        <appender-ref ref="FILE" />   
      </root>   
    </configuration>  
按照固定窗口模式生成日志文件

在文件大小超过20MB时,我们需要生成一个新的日志文件。同时,我们需要确保窗口大小在1到3之间。当我们保存了3个归档文件后,最早的日志将被覆盖。

此外,我们还有两个主要任务需要完成。首先,我们需要将日志信息转换成字节数组的形式。其次,我们需要将这些字节数组写入输出流中。

     <configuration>   
      <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">   
        <file>test.log</file>   
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">   
          <fileNamePattern>tests.%i.log.zip</fileNamePattern>   
          <minIndex>1</minIndex>   
          <maxIndex>3</maxIndex>   
        </rollingPolicy>   
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">   
          <maxFileSize>5MB</maxFileSize>   
        </triggeringPolicy>   
        <encoder>   
          <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>   
        </encoder>   
      </appender>   
               
      <root level="DEBUG">   
        <appender-ref ref="FILE" />   
      </root>   
    </configuration>  

常用配置详解 <encoder>

目前,我们使用的是PatternLayoutEncoder作为唯一有用且默认的编码器。在配置文件中,我们可以通过<pattern>节点来设置日志的输入格式。使用“%”加上转换符的方式,我们可以定义不同的日志输出格式。如果我们想要输出一个百分号“%”,那么我们需要使用反斜杠“\”对“%”进行转义。

     <encoder>   
       <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>   
    </encoder>  
里面的转换符说明:
输出logger名称

输出日志的logger名,可有一个整形参数,功能是缩短logger名,设置为0表示只输入logger最右边点符号之后的字符串。在这里插入图片描述

转移符号Logger nameResult
%loggermainPackage.sub.sample.BarmainPackage.sub.sample.Bar
%logger{0}mainPackage.sub.sample.BarBar
%logger{5}mainPackage.sub.sample.Barm.s.s.Bar
%logger{10}mainPackage.sub.sample.Barm.s.s.Bar
%logger{15}mainPackage.sub.sample.Barm.s.sample.Bar
%logger{16}mainPackage.sub.sample.Barm.sub.sample.Bar
%logger{26}mainPackage.sub.sample.BarmainPackage.sub.sample.Bar
输出Class类全限定名

输出执行记录请求的调用者的全限定名。参数与上面的一样。尽量避免使用,除非执行速度不造成任何问题。
在这里插入图片描述

输出上下文名称

在Logback中,可以通过contextName属性来获取Logger的上下文名称,通过cn来获取调用者的全限定名。这两个属性可以在Logback的配置文件中进行配置。

要获取contextName属性,可以使用以下方式:

<property name="contextName" value="${logback.contextName}" />

然后,在日志输出中使用${contextName}占位符来获取上下文名称。要获取调用者的全限定名,可以使用以下方式:

<encoder>
    <pattern>%X{cn} - %msg%n</pattern>
</encoder>

在这种情况下,会将调用者的全限定名存储在MDC(Mapped Diagnostic Context)的cn键中,然后在日志输出模式中使用%X{cn}来获取。

日志的打印日期时间

在Logback中,可以使用%d{pattern}来指定日期的格式化模式,其中pattern与SimpleDateFormat中的模式语法相同。
在这里插入图片描述
以下是一些常用的日期格式化模式示例:

  • %d{yyyy-MM-dd HH:mm:ss}:输出格式为年-月-日 时:分:秒
  • %d{yyyy/MM/dd HH:mm:ss}:输出格式为年/月/日 时:分:秒
  • %d{yyyy-MM-dd HH:mm:ss.SSS}:输出格式为年-月-日 时:分:秒.毫秒
  • %d{EEE MMM dd HH:mm:ss Z}:输出格式为星期几 月份 日 时:分:秒 时区
  • %d{HH:mm:ss.SSS}:输出格式为时:分:秒.毫秒

你可以根据需要选择合适的日期格式化模式,然后将其放置在Logback配置文件中的适当位置来格式化打印的日志信息中的日期。

Conversion PatternResult
%d2006-10-20 14:06:49,812
%date2006-10-20 14:06:49,812
%date{ISO8601}2006-10-20 14:06:49,812
%date{HH:mm:ss.SSS}14:06:49.812
%date{dd MMM yyyy ;HH:mm:ss.SSS}20 oct. 2006;14:06:49.812
输出信息深度

输出生成日志的调用者的位置信息,整数选项表示输出信息深度。

caller{depth}caller{depth, evaluator-1, ... evaluator-n}
案例分析

%caller{2}

0 [main] DEBUG - logging statement
Caller+0 at mainPackage.sub.sample.Bar.sampleMethodName(Bar.java:22)
Caller+1 at mainPackage.sub.sample.Bar.createLoggingRequest(Bar.java:17)

%caller{3}

16 [main] DEBUG - logging statement
Caller+0 at mainPackage.sub.sample.Bar.sampleMethodName(Bar.java:22)
Caller+1 at mainPackage.sub.sample.Bar.createLoggingRequest(Bar.java:17)
Caller+2 at mainPackage.ConfigTester.main(ConfigTester.java:38)
F / file 输出源文件名
Conversion PatternResult
%F输出执行记录请求的Java源文件名。请谨慎使用,可能会影响执行速度。
其他格式符号
Conversion PatternResult
%L输出执行日志请求的行号。请谨慎使用,可能会影响执行速度。
%m / %msg / %message输出应用程序提供的信息。
%M输出执行日志请求的方法名。请谨慎使用,可能会影响执行速度。
%n输出平台相关的分行符“\n”或者“\r\n”。
%p / %le / %level输出日志级别。
%r / %relative输出从程序启动到创建日志记录的时间,单位是毫秒。
%t / %thread输出产生日志的线程名。
%replace(%p, {r, t})p 为日志内容,r 是正则表达式,将p 中符合r 的内容替换为t。
格式修饰符

格式修饰符与转换符共同使用,可用于对输出进行格式修饰和优化。

第一个可选的修饰符是左对齐标志,用减号“-”表示。接下来是可选的最小宽度修饰符,用十进制数表示。如果字符数小于最小宽度,则会进行左填充或右填充,默认为左填充(即右对齐),使用空格作为填充符。

如果字符数大于最小宽度,则字符永远不会被截断。最大宽度修饰符由点号"."后跟十进制数表示。如果字符数大于最大宽度,则会从头部开始截断。点号后面加上减号“-”和数字表示从尾部截断。

例如,%-4relative 表示将输出从程序启动到创建日志记录的时间进行左对齐,并且最小宽度为4。

配置详解 <filter>

过滤器(<filter>)用于对日志进行过滤。

在这里插入图片描述

执行过滤器会返回一个枚举值,即DENY、NEUTRAL或ACCEPT中的其中一个。

  • 如果返回DENY,则日志将立即被抛弃,不再经过其他过滤器的处理;
  • 如果返回NEUTRAL,则下一个在有序列表中的过滤器会继续处理日志;
  • 如果返回ACCEPT,则日志会立即被处理,不再经过剩余的过滤器。

过滤器可以被添加到<Appender>(日志处理器)中。当为<Appender>添加一个或多个过滤器时,可以使用任意条件对日志进行过滤。如果<Appender>中存在多个过滤器,那么过滤器会按照配置顺序依次执行。

LevelFilter(级别过滤器):

级别过滤器是一种根据日志级别进行过滤的机制。当日志级别与配置的级别相等时,过滤器会根据onMatchonMismatch进行相应的操作。该过滤器包含以下子节点:

  • <level>:用于设置过滤级别。
  • <onMatch>:用于配置符合过滤条件的操作。
  • <onMismatch>:用于配置不符合过滤条件的操作。

例如,将过滤器的日志级别配置为INFO,那么所有INFO级别的日志都会被交给适当的appender进行处理,而非INFO级别的日志则会被过滤掉。

     <configuration>   
      <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">   
        <filter class="ch.qos.logback.classic.filter.LevelFilter">   
          <level>INFO</level>   
          <onMatch>ACCEPT</onMatch>   
          <onMismatch>DENY</onMismatch>   
        </filter>   
        <encoder>   
          <pattern>   
            %-4relative [%thread] %-5level %logger{30} - %msg%n   
          </pattern>   
        </encoder>   
      </appender>   
      <root level="DEBUG">   
        <appender-ref ref="CONSOLE" />   
      </root>   
    </configuration>  
ThresholdFilter(临界值过滤器)

临界值过滤器根据指定的临界值来过滤低于该临界值的日志。当日志级别等于或高于临界值时,过滤器返回NEUTRAL(中性);当日志级别低于临界值时,日志会被拒绝。

例如,如果希望过滤掉所有低于INFO级别的日志,则可以将临界值设置为INFO。如果还有其他需要帮助的地方,请随时告诉我。

     <configuration>   
      <appender name="CONSOLE"   
        class="ch.qos.logback.core.ConsoleAppender">   
        <!-- 过滤掉 TRACE 和 DEBUG 级别的日志-->   
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">   
          <level>INFO</level>   
        </filter>   
        <encoder>   
          <pattern>   
            %-4relative [%thread] %-5level %logger{30} - %msg%n   
          </pattern>   
        </encoder>   
      </appender>   
      <root level="DEBUG">   
        <appender-ref ref="CONSOLE" />   
      </root>   
    </configuration>  
EvaluatorFilter(求值过滤器):

求值过滤器用于评估和鉴别日志是否符合指定条件。

<evaluator>(鉴别器):

鉴别器是常用的求值器,其中最常用的是JaninoEventEvaluator(Janino事件评估器),也是默认的鉴别器。它接受一个任意的 Java 布尔值表达式作为求值条件,并在配置文件解释过程中动态编译这个布尔值表达式。如果布尔值表达式返回 true,则表示符合过滤条件。evaluator 还包含一个子标签 <expression>,用于配置求值条件。

求值表达式作用于当前日志,logback向求值表达式暴露日志的各种字段:

NameTypeDescription
eventLoggingEvent与记录请求相关联的原始记录事件,下面所有变量都来自 event,例如,event.getMessage() 返回下面 “message” 相同的字符串
messageString日志的原始消息,例如,设有 logger mylogger,“name” 的值是 “AUB”,对于 mylogger.info(“Hello {}”,name); “Hello {}” 就是原始消息
formattedMessageString日志被格式化的消息,例如,设有 logger mylogger,“name” 的值是 “AUB”,对于 mylogger.info(“Hello {}”,name); “Hello Aub” 就是格式化后的消息
loggerStringlogger 名
loggerContextLoggerContextVO日志所属的 logger 上下文
levelint级别对应的整数值,所以 level > INFO 是正确的表达式
timeStamplong创建日志的时间戳
markerMarker与日志请求相关联的 Marker 对象,注意 “Marker” 有可能为 null,所以你要确保它不能是 null
mdcMap包含创建日志期间的 MDC 所有值的 map。访问方法是:mdc.get(“myKey”) 。mdc.get() 返回的是 Object 不是 String,要想调用 String 的方法就要强转
throwablejava.lang.Throwable如果没有异常与日志关联,“throwable” 变量为 null。不幸的是,“throwable” 不能被序列化。在远程系统上永远为 null。对于与位置无关的表达式请使用下面的变量 throwableProxy
throwableProxyIThrowableProxy与日志事件关联的异常代理。如果没有异常与日志事件关联,则变量 “throwableProxy” 为 null。当异常被关联到日志事件时,“throwableProxy” 在远程系统上不会为 null
过滤掉所有日志消息中不包含“billing”字符串的日志
     <configuration>   
       
      <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">   
        <filter class="ch.qos.logback.core.filter.EvaluatorFilter">         
          <evaluator> <!-- 默认为 ch.qos.logback.classic.boolex.JaninoEventEvaluator -->   
            <expression>return message.contains("billing");</expression>   
          </evaluator>   
          <OnMatch>ACCEPT </OnMatch>  
          <OnMismatch>DENY</OnMismatch>  
        </filter>   
        <encoder>   
          <pattern>   
            %-4relative [%thread] %-5level %logger - %msg%n   
          </pattern>   
        </encoder>   
      </appender>   
       
      <root level="INFO">   
        <appender-ref ref="STDOUT" />   
      </root>   
    </configuration>  

文章来源:https://blog.csdn.net/l569590478/article/details/135006910
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。