Webx3日志系统的配置

Webx3日志系统的配置

1.前言

日志系统是一个应用中必备的部分,提供了查看错误信息、了解系统状态的最直接手段。

本文介绍了基于Webx3框架的应用如何配置、使用日志系统的方法。

2.名词解释

2.1.日志系统(Logging System)

Log4j —— Apache log4j,较早出现的比较成功的日志系统是Log4j。Log4j开创的日志系统模型(Logger/Appender/Level)行之有效,并一直延用至今。

JUL(java.util.logging.*) —— JDK1.4是第一个自带日志系统的JDK,简称(JUL)。JUL并没有明显的优势来战胜Log4j,反而造成了标准的混乱 —— 采用不同日志系统的应用程序无法和谐共存。

Logback ——是较新的日志系统。它是Log4j的作者吸取多年的经验教训以后重新做出的一套系统。它的使用更方便,功能更强,而且性能也更高。Logback不能单独使用,必须配合日志框架SLF4J(Simple logging facadefor Java)来使用。

2.2.日志框架(Logging Framework)

JUL诞生以后,为了克服多种日志系统并存所带来的混乱,就出现了“日志框架”。日志框架本身不提供记录日志的功能,它只提供了日志调用的接口。日志框架依赖于实际的日志系统如Log4j或JUL来产生真实的日志。

使用日志框架的好处是:应用的部署者可以决定使用哪一种日志系统(Log4j还是JUL),或者在多种日志系统之间切换,而不需要更改应用的代码。

JCL(Jakarta Commons Logging) —— 这是目前最流行的一个日志框架,由Apache Jakarta社区提供。Spring框架、Webx2框架都依赖于JCL。

SLF4J ——这是一个最新的日志框架,由Log4j的作者推出。SLF4J提供了新的API,特别用来配合Logback的新功能。但SLF4J同样兼容Log4j。

由于Log4j原作者的感召力,SLF4J和Logback很快就流行起来。Webx的新版本也决定使用SLF4J作为其日志框架;并推荐Logback作为日志系统,但同时支持Log4J。

3.在Maven中组装日志系统

要在应用中使用日志系统,必须把正确的jar包组装起来。本文假设你的应用是用maven构建的。对于其它构建系统如antx,原理相同,依此类推即可。

如图所示,组装完整的日志系统将涉及如下jar包:

1.        日志框架SLF4J —— Webx3框架以及所有新应用,直接依赖于SLF4J。

2.        日志框架JCL —— Webx2框架、Spring框架、以及以前的老应用,都使用JCL来输出日志。好在SLF4J提供了一个“桥接”包:JCL-over-SLF4J,它重写了JCL的API,并将所有日志输出转向SLF4J。这样就避免了两套日志框架并存的问题。

3.        日志系统Logback —— Webx3推荐使用logback来取代log4j。Logback可直接被SLF4J识别并使用。

4.        日志系统Log4j —— 由于客观原因,有些系统暂时不能升级到Logback。好在SLF4J仍然支持Log4j。Log4j需要一个适配器slf4j-log4j12才能被SLF4J识别并使用。

一山不容二虎,除非一公一母。

l 由于JCL-over-SLF4J和原来的JCL具有完全相同的API,因此两者是不能共存的

l Logback和slf4j-log4j12也不能并存,否则SLF4J会迷惑并产生不确定的结果。

3.1.在Maven中配置logback作为日志系统

书写pom.xml如下所示。

<dependencies>

    <dependency>

        <groupId>org.slf4j</groupId>

        <artifactId>slf4j-api</artifactId>

    </dependency>

    <dependency>

        <groupId>org.slf4j</groupId>

        <artifactId>jcl-over-slf4j</artifactId>

    </dependency>

    <dependency>

        <groupId>ch.qos.logback</groupId>

        <artifactId>logback-classic</artifactId>

    </dependency>

</dependencies>

 

<!--

  - 注意,如果你的项目指定了parentpom,那么建议

  - 把以下的<dependencyManagement>放在parent pom中,以便多个子项目共享配置。

 -->

<dependencyManagement>

    <dependencies>

        <dependency>

            <groupId>org.slf4j</groupId>

            <artifactId>slf4j-api</artifactId>

            <version>1.5.11</version>

        </dependency>

        <dependency>

            <groupId>org.slf4j</groupId>

            <artifactId>jcl-over-slf4j</artifactId>

            <version>1.5.11</version>

        </dependency>

        <dependency>

            <groupId>commons-logging</groupId>

            <artifactId>commons-logging</artifactId>

            <version>99.0-does-not-exist</version>

        </dependency>

        <dependency>

            <groupId>ch.qos.logback</groupId>

            <artifactId>logback-classic</artifactId>

            <version>0.9.18</version>

            <scope>runtime</scope>

        </dependency>

    </dependencies>

</dependencyManagement>

请注意以下几点:

l 把所依赖jar包的版本定义在<dependencyManagement>中,而不是<dependencies>中。因为前者可影响间接依赖,后者只能影响直接依赖。

l 将日志系统的依赖设定为<scope>runtime</scope>,因为应用程序永远不需要直接调用日志系统,而是通过SLF4J或JCL这样的日志框架来调用它们。

l 将commons-logging的依赖版本设定为“<version>99.0-does-not-exist</version>”。这是一个特殊的版本 —— 事实上这个版本的jar包里空无一物。由于maven缺少一个重要的功能 —— 它不能全局地排除一个jar包依赖,所以SLF4J的作者为我们建议了这样一种解决方法。

l 最后,你需要在项目文件夹下面,执行一下命令:“mvn dependency:tree”,确保没有jar包间接依赖了slf4j-log4j12。如果有的话,你可以用下面的配置来排除掉。

<dependencyManagement>

    <dependencies>

        <dependency>

            <groupId>yourGroupId</groupId>

            <artifactId>yourArtifactId</artifactId>

            <version>yourVersion</version>

            <exclusions>

                <exclusion>

                   <groupId>org.slf4j</groupId>

                   <artifactId>slf4j-log4j12</artifactId>

                </exclusion>

            </exclusions>

        </dependency>

    </dependencies>

</dependencyManagement>

3.2.在Maven中配置log4j作为日志系统

书写pom.xml如下所示。

<dependencies>

    <dependency>

        <groupId>org.slf4j</groupId>

        <artifactId>slf4j-api</artifactId>

    </dependency>

    <dependency>

        <groupId>org.slf4j</groupId>

        <artifactId>jcl-over-slf4j</artifactId>

    </dependency>

    <dependency>

        <groupId>org.slf4j</groupId>

        <artifactId>slf4j-log4j12</artifactId>

    </dependency>

</dependencies>

 

<!--

  - 注意,如果你的项目指定了parentpom,那么建议

  - 把以下的<dependencyManagement>放在parent pom中,以便多个子项目共享配置。

 -->

<dependencyManagement>

    <dependencies>

        <dependency>

            <groupId>org.slf4j</groupId>

            <artifactId>slf4j-api</artifactId>

            <version>1.5.11</version>

        </dependency>

        <dependency>

            <groupId>org.slf4j</groupId>

            <artifactId>jcl-over-slf4j</artifactId>

            <version>1.5.11</version>

        </dependency>

        <dependency>

            <groupId>commons-logging</groupId>

            <artifactId>commons-logging</artifactId>

            <version>99.0-does-not-exist</version>

        </dependency>

        <dependency>

            <groupId>org.slf4j</groupId>

            <artifactId>slf4j-log4j12</artifactId>

            <version>1.5.11</version>

            <scope>runtime</scope>

        </dependency>

        <dependency>

            <groupId>log4j</groupId>

            <artifactId>log4j</artifactId>

            <version>1.2.15</version>

            <scope>runtime</scope>

        </dependency>

    </dependencies>

</dependencyManagement>

除了在前文中所讲到注意点以外,你还需要注意以下几点:

l 你需要在项目文件夹下面,执行一下命令:“mvn dependency:tree”,确保没有jar包间接依赖了logback-classic。如果有的话,你可以用下面的配置来排除掉。

<dependencyManagement>

    <dependencies>

        <dependency>

            <groupId>yourGroupId</groupId>

            <artifactId>yourArtifactId</artifactId>

            <version>yourVersion</version>

            <exclusions>

                <exclusion>

                   <groupId>ch.qos.logback</groupId>

                   <artifactId>logback-classic</artifactId>

                </exclusion>

            </exclusions>

        </dependency>

    </dependencies>

</dependencyManagement>

4.在WEB应用中配置日志系统

4.1.设置WEB-INF/web.xml

请参照下面的设置:

<?xml version="1.0"encoding="UTF-8"?>

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

   xsi:schemaLocation="

       http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd

   ">

 

    <context-param>

       <param-name>loggingRoot</param-name>

       <param-value>/tmp/logs</param-value>

    </context-param>

    <context-param>

       <param-name>loggingLevel</param-name>

       <param-value>INFO</param-value>

    </context-param>

    <context-param>

       <param-name>loggingCharset</param-name>

       <param-value>UTF-8</param-value>

    </context-param>

 

    <listener>

       <listener-class>com.alibaba.citrus.logconfig.LogConfiguratorListener</listener-class>

    </listener>

 

    <filter>

       <filter-name>mdc</filter-name>

       <filter-class>com.alibaba.citrus.webx.servlet.SetLoggingContextFilter</filter-class>

    </filter>

 

    <filter-mapping>

       <filter-name>mdc</filter-name>

       <url-pattern>/*</url-pattern>

    </filter-mapping>

 

</web-app>

配置文件包含几部分:

l 日志参数,定义在<context-param>中。可以在这里定义以下参数:

n loggingRoot —— 保存日志文件的根目录。如不指定,默认为:“${user.home}/logs”。

n loggingCharset —— 用来生成日志文件的字符集编码。如不指定,默认为当前系统的默认字符集编码。

n loggingLevel —— 日志级别,低于指定级别的日志将不被输出。如不指定,默认为“INFO”。

n log* —— 名称以“log”开头的任意参数,都将被视作日志系统的参数。

通常,我们可以利用maven的filtering机制,或者autoconfig插件来生成web.xml文件,以便定制上述参数。

l LogConfiguratorListener —— 在WEB应用启动的时候,这个listener会被激活,并初始化日志系统。LogConfiguratorListener会自动识别当前的日志系统是什么:

n 假如你在maven的pom.xml中指定log4j为日志系统,那么该listener就会试图用WEB-INF/log4j.xml来初始化日志系统。

n 假如你在maven的pom.xml中指定logback为日志系统,那么该listener就会试图用WEB-INF/logback.xml来初始化日志系统。

n 假如你在maven的pom.xml中未指定任何日志系统(不存在logback-classic或slf4j-log4j12),那么listener会报错并失败,整个WEB应用会退出,服务器报告应用启动失败。

n 假如WEB-INF/logback.xml(或log4j.xml)不存在,那么listener会用默认的配置文件来初始化日志。默认的配置会把WARN级别以上的日志打印在STDERR中,把WARN级别以下的日志打印在STDOUT中。默认只输出INFO及更高级别的日志。

l SetLoggingContextFilter —— 将当前请求的信息放到日志系统的MDC中(MappedDiagnostic Context)。这样,日志系统就可以打印出诸如下面所示的日志信息:

30377 [2010-06-02 15:24:29] -GET /wrongpage.htm [ip=127.0.0.1, ref=, ua=Mozilla/5.0 (Macintosh; U; Intel MacOS X 10.6; zh-CN; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3, sid=scnd32ei11a7]……

上面的日志中包含了如下信息:

1.        用户请求的类型:GET

2.        请求的URI:/wrongpage.htm

3.        用户IP:127.0.0.1

4.        上一个页面的链接referrer:如有,则显示之

5.        用户的浏览器:Mac版的mozilla浏览器

6.        Session ID:scnd32ei11a7

用户客户端的详细信息,对于发现和追踪错误非常有帮助。

SetLoggingContextFilter是一个可选的filter —— 即使没有它,Webx3.0 <setLoggingContext /> valve也会做同样的事情。但是把这些信息放在filter中,有利于及早记录用户的信息。

4.2.定制WEB-INF/logback.xml(或log4j.xml)

4.2.1.可用的参数占位符

在日志配置文件中,你可以使用以下占位符:

l 在WEB-INF/web.xml中定义的所有日志参数:

n ${loggingRoot}

n ${loggingCharset}

n ${loggingLevel}

n ${log*} —— “*”代表任意名称

l 由系统自动取得的参数:

n ${loggingHost} —— 代表当前的服务器名称

n ${loggingAddress} —— 代表当前的服务器IP地址

4.2.2.可用的MDC参数占位符

在appender pattern中,你可以使用以下MDC参数:

l %X{method} —— 请求类型,如:GET或POST

l %X{requestURL} —— 完整的URL,如:http://localhost/test

l %X{requestURLWithQueryString} —— 完整的URL,以及querystring,如:http://localhost/test?id=1

l %X{requestURI} —— 不包括host信息的URI,如:/test

l %X{requestURIWithQueryString} —— 不包括host信息的URI,以及query string,如:/test?id=1

l %X{queryString} —— URL参数,如:id=1

l %X{remoteAddr} —— 客户端地址

l %X{remoteHost} —— 客户端域名

l %X{userAgent} —— 客户端浏览器信息

l %X{referrer} —— 上一个页面的URL

l %X{cookies} —— 所有cookies的名称,如:[cookie1, cookie2]

l %X{cookie.*} —— 特定cookie的值,如:%X{cookie.JSESSIONID},将显示当前session的ID

l 其它由应用程序或框架置入MDC的参数。

4.2.3.WEB-INF/logback.xml示例

下面是logback.xml配置文件的示例,仅供参考。详细配置方法请见logback官方文档。

请特别留意参数占位符的写法,如“${loggingRoot}”;此外,还有包含MDC占位符的appender pattern的写法,如:“%X{method}”、“%X{requestURIWithQueryString}”等。

<?xml version="1.0" encoding="UTF-8"?>

<configuration debug="false">

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

        <target>System.out</target>

        <encoding>${loggingCharset}</encoding>

        <layout class="ch.qos.logback.classic.PatternLayout">

            <pattern><![CDATA[

%n%-4r [%d{yyyy-MM-dd HH:mm:ss}] - %X{method}%X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer},ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n

            ]]></pattern>

        </layout>

        <filter class="com.alibaba.citrus.logconfig.logback.LevelRangeFilter">

            <levelMax>INFO</levelMax>

        </filter>

    </appender>

 

    <appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">

        <target>System.err</target>

        <encoding>${loggingCharset}</encoding>

        <layout class="ch.qos.logback.classic.PatternLayout">

            <pattern><![CDATA[

%n%-4r [%d{yyyy-MM-dd HH:mm:ss}] - %X{method}%X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer},ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n

            ]]></pattern>

        </layout>

        <filter class="com.alibaba.citrus.logconfig.logback.LevelRangeFilter">

            <levelMin>WARN</levelMin>

        </filter>

    </appender>

 

    <appender name="PROJECT" class="ch.qos.logback.core.FileAppender">

        <file>${loggingRoot}/${localHost}/petstore.log</file>

        <encoding>${loggingCharset}</encoding>

        <append>false</append>

        <layout class="ch.qos.logback.classic.PatternLayout">

            <pattern><![CDATA[

%n%-4r [%d{yyyy-MM-dd HH:mm:ss}] - %X{method}%X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer},ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n

            ]]></pattern>

        </layout>

    </appender>

 

    <root>

        <level value="${loggingLevel}"/>

        <appender-ref ref="STDERR"/>

        <appender-ref ref="STDOUT"/>

        <appender-ref ref="PROJECT"/>

    </root>

</configuration>

4.2.4.WEB-INF/log4j.xml示例

下面是log4j.xml配置文件的示例,仅供参考。详细配置方法请见log4j官方文档。

请特别留意参数占位符的写法,如“${loggingRoot}”;此外,还有包含MDC占位符的appender pattern的写法,如:“%X{method}”、“%X{requestURIWithQueryString}”等。

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">

        <param name="target" value="System.out"/>

        <param name="encoding" value="${loggingCharset}"/>

        <layout class="org.apache.log4j.PatternLayout">

            <param name="ConversionPattern"

                value="%n%-4r[%d{yyyy-MM-dd HH:mm:ss}] - %X{method} %X{requestURIWithQueryString}[ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent},sid=%X{cookie.JSESSIONID}]%n  %-5level%logger{35} - %m%n"

             />

        </layout>

        <filter class="org.apache.log4j.varia.LevelRangeFilter">

            <param name="levelMax" value="INFO"/>

        </filter>

    </appender>

 

    <appender name="STDERR" class="org.apache.log4j.ConsoleAppender">

        <param name="target" value="System.err"/>

        <param name="encoding" value="${loggingCharset}"/>

        <layout class="org.apache.log4j.PatternLayout">

            <param name="ConversionPattern"

                value="%n%-4r[%d{yyyy-MM-dd HH:mm:ss}] - %X{method} %X{requestURIWithQueryString}[ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n  %-5level %logger{35} - %m%n"

             />

        </layout>

        <filter class="org.apache.log4j.varia.LevelRangeFilter">

            <param name="levelMin" value="WARN"/>

        </filter>

    </appender>

 

    <appender name="PROJECT" class="org.apache.log4j.FileAppender">

        <param name="file" value="${loggingRoot}/${localHost}/myapp.log"/>

        <param name="encoding" value="${loggingCharset}"/>

        <param name="append" value="true"/>

        <layout class="org.apache.log4j.PatternLayout">

            <param name="ConversionPattern"

                value="%n%-4r[%d{yyyy-MM-dd HH:mm:ss}] - %X{method} %X{requestURIWithQueryString}[ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n  %-5level %logger{35} - %m%n"

             />

        </layout>

    </appender>

 

    <root>

        <level value="${loggingLevel}"/>

        <appender-ref ref="STDOUT"/>

        <appender-ref ref="STDERR"/>

        <appender-ref ref="PROJECT"/>

    </root>

</log4j:configuration>

5.提示信息

当LogConfiguratorListener启动时,将会在STDERR中打印信息,像下面这个样子:

2010-06-0216:57:28.021:INFO:/:Initializing log4j system

INFO: configuring"log4j" using file:/Users/…/WEB-INF/log4j.xml

 - with property localAddress = 10.16.58.5

 - with property localHost =baobao-macbook-pro.local

 - with property loggingCharset = UTF-8

 - with property loggingLevel = warn

 - with property loggingRoot = /tmp/logs

通过这些信息,你可以检查如下内容:

l 是否选择了正确的日志系统,如:log4j或logback

l 是否选择了正确的日志配置文件,如:log4j.xml

l 日志文件的根目录、字符集编码、日志级别等信息。

6.常见错误

6.1.No logsystem exists

报这个错的原因可能是:

l 不存在slf4j-log4j12、logback-classic等任何一个日志系统的实现

l Slf4j的版本和日志系统的版本不匹配,例如,slf4j为1.4.3版,而slf4j-log4j12为1.5.11版。

解决方法:

l 用mvn dependency:tree查看所有的依赖包,排除以上错误。

l 查看服务器环境(如jboss),查看是不是存在不正确版本的jar包,被优先于应用jar包而加载了。

提示:查看真实jar包有一个简单有效的方法,就是在应用程序的任意点设置断点(利用eclipse远程调试功能),当系统停留在断点处时,执行如下的java代码,查看其值:

getClass().getClassLoader().getResource(getClass().getName().replace('.','/') + ".class")

6.2.NoSuchMethodError:org.slf4j.MDC.getCopyOfContextMap()

报这个错的原因是:

l SLF4J的版本过老。MDC.getCopyOfContextMap()方法是从SLF4J 1.5.1时加入的,假如你的SLF4J是之前的版本,就会报错。

解决方法:

l 用mvn dependency:tree查看所有的依赖包,排除以上错误。

l 查看服务器环境(如jboss),查看是不是存在不正确版本的jar包,被优先于应用jar包而加载了。

6.3.Class pathcontains multiple SLF4J bindings

SLF4J报如下错误:

SLF4J: Class pathcontains multiple SLF4J bindings.

SLF4J: Foundbinding in [jar:file:/…/WEB-INF/lib/logback-classic-0.9.18.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: Foundbinding in [jar:file:/…/WEB-INF/lib/slf4j-log4j12-1.5.11.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: Seehttp://www.slf4j.org/codes.html#multiple_bindings for an explanation.

这个错的原因是classpath中存在多个日志系统,使SLF4J无所适从。

解决方法:

SLF4J已经列出了classpath中所有的日志系统的位置。根据这些信息,你可以调整应用的依赖,或者整理服务器的环境,使之只剩下一个日志系统。

6.4.看不到日志输出

原因可能是日志的配置文件可能有错。

解决方法:

l 首先,查看LogConfiguratorListener输出到STDERR中的信息,确定系统:1) 选择了正确的日志系统;2) 选择了正确的配置文件;3) 设置了正确的参数(loggingRoot、loggingLevel等)

提示:在JBOSS环境中,STDOUT和STDERR会被重定向到Log4j中,然后被输出到一个文件中,通常是log/server.log。你必须从这个日志文件中查看LogConfiguratorListener的输出。

l 假如以上信息均正确,查看日志配置文件log4j.xml或logback.xml,是否使用了正确的参数占位符,例如:${loggingRoot}、${loggingLevel}等。

l 检查文件系统权限,确保应用有权限创建和修改日志文件。

提示:假设你使用log4j为日志系统。在JBOSS环境中,当log4j被初始化后,STDOUT和STDERR会被重新配置到不同的appender中。原先用来记录STDOUT和STDERR的日志文件log/server.log将不会再被使用。建议你设置log4j.xml,增加如下内容:

<appender name="JBOSS_APPENDER" class="org.apache.log4j.FileAppender">

    <param name="file" value="${loggingRootJboss}/server.log"/>

    <param name="encoding" value="${loggingCharset}"/>

    <param name="append" value="true"/>

    <layout class="org.apache.log4j.PatternLayout">

        <param name="ConversionPattern"

            value="%n%-4r[%d{yyyy-MM-dd HH:mm:ss}] - %X{method} %X{requestURIWithQueryString}[ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent},sid=%X{cookie.JSESSIONID}]%n  %-5level%logger{35} - %m%n"

         />

    </layout>

</appender>

 

<logger name="STDOUT">

    <appender-ref ref="JBOSS_APPENDER"/>

</logger>

<logger name="STDERR">

    <appender-ref ref="JBOSS_APPENDER"/>

</logger>

这里用到了一个新的变量:${loggingRootJboss},你需要把它定义在WEB-INF/web.xml中。

7.结尾的话

Webx3中的LogConfigurator目前只提供了logback和log4j的支持,尽管支持一种新的日志系统是非常容易的,但现在看来,这两种日志系统已经足够我们使用了。

然而SLF4J还有更多的功能:

l 除了log4j和logback以外,SLF4J还支持几种其它的日志系统;

l 除了jcl-over-slf4j以外,SLF4J还提供了几种对其它legacy日志系统的桥接功能。

详情请见SLF4J的文档:http://www.slf4j.org/docs.html

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值