logback日志框架学习(3)configuration配置文件

Configuration at initialization

Inserting log requests into the application code requires a fair amount of planning and effort. Observation shows that approximately four percent of code is dedicated to logging. Consequently, even a moderately sized application will contain thousands of logging statements embedded within its code. Given their number, we need tools to manage these log statements.

Logback can be configured either programmatically or with a configuration script expressed in XML, Groovy or serialized model format. By the way, existing log4j users can convert their log4j.properties files to logback.xml using our PropertiesTranslator web-application.

将日志请求插入到应用程序代码中需要相当多的计划和精力。观察表明,大约4%的代码专门用于日志记录。因此,即使是中等大小的应用程序,其代码中也会包含数千条日志记录语句。考虑到它们的数量,我们需要工具来管理这些日志语句。
Logback可以通过编程方式进行配置,也可以使用以XML、Groovy或序列化模型格式表示的配置脚本进行配置。顺便说一句,现有的log4j用户可以使用我们的PropertiesTranslator web应用程序将他们的log4j.properties文件转换为logback.xml。

Let us begin by discussing the initialization steps that logback follows to try to configure itself:

  1. Logback will search for any custom Configurator providers using
    service-provider loading facility. If any such custom provider is
    found, it takes precedence over logback’s own configurators, e.g.
    DefaultJoranConfigurator (see below). A custom Configurator is an
    implementation of ch.qos.logback.classic.spi.Configurator interface.
    Custom configurators are searched by looking up file resources
    located under
    META-INF/services/ch.qos.logback.classic.spi.Configurator. The
    contents of this file should specify the fully qualified class name
    of the desired Configurator implementation.
  2. SINCE 1.3.9/1.4.9 If no user-supplied custom Configurator was found
    in the previous step, logback will instantiate a
    SerializedModelConfigurator.
    • If the system property “logback.serializedModelFile” is set, then
    SerializedModelConfigurator will try to locate the file specified
    the aforementioned system property. If the designated file could be
    located, it will be read and interpreted for configuration.
    • If the aforementioned system property was not set or if the
    designated file could not be found, SerializedModelConfigurator will
    search for the serialized configuration model file logback-test.scmo
    in the classpath. If this file can be located, it will be read and
    interpreted for configuration.
    • If the aforementioned file cannot be found,
    SerializedModelConfigurator will search for the serialized
    configuration model file logback.scmo in the classpath.
    • If no serialized configuration model file could be located,
    SerializedModelConfigurator will return with an execution status
    asking for the next available configurator, i.e.
    DefaultJoranConfigurator, to be invoked.
    Configuration from a serialized model file executes faster and does
    not require any XML libraries. In conjunction with GraalVM, this may
    yield smaller executables that start faster.
  3. NOMINAL STEP If the previous configurators could not locate their
    required resources, then an instance of DefaultJoranConfigurator
    will be created and invoked.
    • If the system property “logback.configurationFile” is set, then
    DefaultJoranConfigurator will try to locate the file specified the
    aforementioned system property. If this file can be located, it will
    be read and interpreted for configuration.
    • If the previous step fails, DefaultJoranConfigurator will try to
    locate the configuration file “logback-test.xml” on the classpath.
    If this file can be located, it will be read and interpreted for
    configuration.
    • If no such file is found, it will try to locate the configuration
    file “logback.xml” in the classpath. If this file can be located, it
    will be read and interpreted for configuration. Note that this is
    the nominal configuration step.
    • If no configuration file could be located,
    DefaultJoranConfigurator will return with an execution status asking
    for the next available configurator, i.e. BasicConfigurator, to be
    invoked.

If none of the above succeeds, logback-classic will configure itself using the BasicConfigurator which will cause logging output to be directed to the console.

2.从1.3.9和1.4.9 多了一个SerializedModelConfigurator.
在这里插入图片描述
注意高版本logback 1.4.9有,之前1.2.4没有serialized和defaultjoran类。

Automatically configuring logback

The simplest way to configure logback is by letting logback fall back to its default configuration. Let us give a taste of how this is done in an imaginary application called MyApp1.
前面有点复杂直接看demo
(logback-examples/src/main/java/chapters/configuration/MyApp1.java)

package chapters.configuration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyApp1 {
 final static Logger logger = LoggerFactory.getLogger(MyApp1.class);

 public static void main(String[] args) {
   logger.info("Entering application.");

   Foo foo = new Foo();
   foo.doIt();
   logger.info("Exiting application.");
 }
}

(logback-examples/src/main/java/chapters/configuration/Foo.java)

package chapters.configuration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Foo {
  static final Logger logger = LoggerFactory.getLogger(Foo.class);

  public void doIt() {
    logger.debug("Did it again!");
  }
}

pom引入jar

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.7</version>
        </dependency>

打印结果

17:18:04.095 [main] INFO com.chenchi.log.chapter3.MyApp1 -- Entering application.
17:18:04.108 [main] DEBUG com.chenchi.log.chapter3.Foo -- Did it again!
17:18:04.108 [main] INFO com.chenchi.log.chapter3.MyApp1 -- Exiting application.

Assuming the configuration files logback-test.xml or logback.xml are not present, logback will default to invoking BasicConfigurator which will set up a minimal configuration. This minimal configuration consists of a ConsoleAppender attached to the root logger. The output is formatted using a PatternLayoutEncoder set to the pattern %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n. Moreover, by default the root logger is assigned the DEBUG level.

假设配置文件logback-test.xml或logback.xml不存在,logback将默认为调用BasicConfigurator,它将设置最小配置。此最低配置包含一个连接到根记录器的控制台附件。输出使用PatternLayoutEncoder进行格式化,该编码器设置为模式 %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n。此外,默认情况下,根记录器被分配DEBUG级别。
在这里插入图片描述
三段分别设置了appender layout 和level

Configuration with logback-test.xml or logback.xml

(logback-examples/src/main/resources/chapters/configuration/sample0.xml)

<configuration>
<!-- STDOUT 这个是appendername 可以随便取 sout print啥都行-->
<!-- ch.qos.logback.core.ConsoleAppender 是具体的类 一般用自带的 也可以自定义-->
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

After you have renamed sample0.xml as logback.xml (or logback-test.xml) place it into a directory accessible from the class path. Running the MyApp1 application should give identical results to its previous run.
你改名为logback.xml或者logback-test.xml 放到classpath时再跑一遍。

Automatic printing of status messages in case of warning or errors

如果在解析配置文件的过程中出现警告或错误,logback将自动在控制台上打印其内部状态消息。

public static void main(String[] args) {
  // assume SLF4J is bound to logback in the current environment
  LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
  // print logback's internal status
  StatusPrinter.print(lc);
  ...
}

打印日志

17:29:25.146 [main] INFO  [com.chenchi.log.chapter3.MyApp2] [-]- Entering application.
17:29:25.160 [main] DEBUG [com.chenchi.log.chapter3.Foo] [-]- Did it again!
17:29:25.160 [main] INFO  [com.chenchi.log.chapter3.MyApp2] [-]- Exiting application.
17:29:24,747 |-INFO in ch.qos.logback.classic.LoggerContext[default] - This is logback-classic version 1.3.9
17:29:24,751 |-INFO in ch.qos.logback.classic.util.ContextInitializer@617faa95 - No custom configurators were discovered as a service.
17:29:24,751 |-INFO in ch.qos.logback.classic.util.ContextInitializer@617faa95 - Trying to configure with ch.qos.logback.classic.joran.SerializedModelConfigurator
17:29:24,752 |-INFO in ch.qos.logback.classic.util.ContextInitializer@617faa95 - Constructed configurator of type class ch.qos.logback.classic.joran.SerializedModelConfigurator
17:29:24,761 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.scmo]
17:29:24,761 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.scmo]
17:29:24,762 |-INFO in ch.qos.logback.classic.util.ContextInitializer@617faa95 - ch.qos.logback.classic.joran.SerializedModelConfigurator.configure() call lasted 10 milliseconds. ExecutionStatus=INVOKE_NEXT_IF_ANY
17:29:24,762 |-INFO in ch.qos.logback.classic.util.ContextInitializer@617faa95 - Trying to configure with ch.qos.logback.classic.util.DefaultJoranConfigurator
17:29:24,763 |-INFO in ch.qos.logback.classic.util.ContextInitializer@617faa95 - Constructed configurator of type class ch.qos.logback.classic.util.DefaultJoranConfigurator
17:29:24,763 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback-test.xml] at [file:/D:/install/code/learning/bigdata_learining/log/logback/target/classes/logback-test.xml]
17:29:25,054 |-INFO in ch.qos.logback.core.model.processor.AppenderModelHandler - Processing appender named [STDOUT]
17:29:25,054 |-INFO in ch.qos.logback.core.model.processor.AppenderModelHandler - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
17:29:25,063 |-INFO in ch.qos.logback.core.model.processor.ImplicitModelHandler - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
17:29:25,119 |-INFO in ch.qos.logback.classic.model.processor.RootLoggerModelHandler - Setting level of ROOT logger to DEBUG
17:29:25,119 |-INFO in ch.qos.logback.core.model.processor.AppenderRefModelHandler - Attaching appender named [STDOUT] to Logger[ROOT]
17:29:25,119 |-INFO in ch.qos.logback.core.model.processor.DefaultProcessor@1e127982 - End of configuration.
17:29:25,120 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@60c6f5b - Registering current configuration as safe fallback point
17:29:25,120 |-INFO in ch.qos.logback.classic.util.ContextInitializer@617faa95 - ch.qos.logback.classic.util.DefaultJoranConfigurator.configure() call lasted 357 milliseconds. ExecutionStatus=DO_NOT_INVOKE_NEXT_IF_ANY

这种适合于 有时候引入各种日志jar冲突了,又不知道哪里冲突了或者各种日志配置文件xml properties一堆不知道哪个起作用了,打印内核信息,可以快速定位

Status data

启用状态数据的输出通常对诊断logback问题有很大帮助
(logback-examples/src/main/resources/chapters/configuration/onConsoleStatusListener.xml)
status data 这个就是强行打印 context
等同于
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// print logback’s internal status
StatusPrinter.print(lc);

Automatically reloading configuration file upon modification

这个比较有用 自动加载更新的配置文件

<configuration scan="true" scanPeriod="30 seconds" >
  ...
</configuration> 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyAppScan {
    final static Logger logger = LoggerFactory.getLogger(MyAppScan.class);

    public static void main(String[] args) throws InterruptedException {
        MyAppScan myAppScan = new MyAppScan();
        while (true){
            myAppScan.printAll();
            Thread.sleep(10000);
        }
    }
    private void printAll(){
        logger.trace("trace");
        logger.debug("debug");
        logger.info("info");
        logger.warn("warn");
        logger.error("error");
        System.out.println("==============================");
    }
}

logback.xml

<configuration scan="true" scanPeriod="15 seconds" >
        <!-- STDOUT 这个是appendername 可以随便取 sout print啥都行-->
    <!-- ch.qos.logback.core.ConsoleAppender 是具体的类 一般用自带的 也可以自定义-->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level [%logger{36}] [-%kvp]- %msg%n</pattern>
        </encoder>
    </appender>
    <!-- status data 这个就是强行打印 context-->
    <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />

    <root level="error">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

注意测试的时候要修改target/class下的xml文件 因为resource的不一定能及时编译
在这里插入图片描述

Enabling packaging data in stack traces

在这里插入图片描述
如果被指示这样做,logback可以包括它输出的堆栈跟踪行的每一行的打包数据。打包数据由jar文件的名称和版本组成,jar文件是堆栈跟踪行的类的来源。打包数据对于识别软件版本控制问题非常有用。然而,计算成本相当高,尤其是在频繁抛出异常的应用程序中。
如何设置

<configuration packagingData="true">
  ...
</configuration>

或者

 LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
  lc.setPackagingDataEnabled(true);

一下子不知道找个啥异常,,感觉就是日志输出的更完善了?

Invoking JoranConfigurator directly

(logback-examples/src/main/java/chapters/configuration/MyApp3.java)
看不出来有啥用

Stopping logback-classic

释放资源


import org.sflf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
...

// assume SLF4J is bound to logback-classic in the current environment
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();
Stopping logback-classic via a shutdown hook

Installing a JVM shutdown hook is a convenient way for shutting down logback and releasing associated resources.

<configuration debug="true">
   <!-- in the absence of the class attribute, assume
   ch.qos.logback.core.hook.DefaultShutdownHook -->
   <shutdownHook/>

   <!-- rest of the config file.. -->

</configuration>

Configuration file syntax

在这里插入图片描述

Case sensitivity of tag names

If you are unsure which case to use for a given tag name, just follow the camelCase convention which is almost always the correct convention.
遵循驼峰命名即可
and are equivalent but not

Configuring loggers, or the element
<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>
        %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n
     </pattern>
    </encoder>
  </appender>

  <logger name="chapters.configuration" level="INFO" />
  <logger name="chapters.configuration.Foo" level="DEBUG" />

  <root level="DEBUG">
    <appender-ref ref="STDOUT" />
  </root>

</configuration>

在这里插入图片描述
这个就是之前说过的level继承 appender其实也可以继承

<configuration>

  <appender name="STDOUT"
   class="ch.qos.logback.core.ConsoleAppender">
   <encoder>
     <pattern>
        %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n
      </pattern>
    </encoder>
  </appender>

  <logger name="chapters.configuration" level="INFO" />
 
  <!-- turn OFF all logging (children can override) -->
  <root level="OFF">
    <appender-ref ref="STDOUT" />
  </root>

</configuration>

在这里插入图片描述

Configuring Appenders

An appender is configured with the element, which takes two mandatory attributes name and class. The name attribute specifies the name of the appender whereas the class attribute specifies the fully qualified name of the appender class to instantiate. The element may contain zero or one elements, zero or more elements and zero or more elements. Apart from these three common elements, elements may contain any number of elements corresponding to JavaBean properties of the appender class. Seamlessly supporting any property of a given logback component is one of the major strengths of Joran as discussed in a later chapter. The following diagram illustrates the common structure. Note that support for properties is not shown in the diagram below.

appender配置有<appender>元素,该元素具有两个强制性属性name和class。name属性指定appender的名称,
class属性指定要实例化的appender类的全路径名称。
<appender>元素可以包含零个或一个<layout>元素、零个或多个<encoder>元素以及零个或更多个<filter>元素。除了这三个常见元素之外,<appender>元素可以包含与appender类的JavaBean属性相对应的任意数量的元素。无缝地支持给定logback组件的任何属性是Joran的主要优势之一,稍后章节将对此进行讨论。下图说明了常见的结构。请注意,下图中没有显示对属性的支持。
在这里插入图片描述

<configuration>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>myApp.log</file>

    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file:%line] -%kvp- %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%kvp %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

这些配置脚本定义了两个名为FILE和STDOUT的appender。
FILE附加程序记录到名为myApp.log的文件。encoder时PatternLayoutEncoder,它输出日期、级别、线程名称、记录器名称、文件名和日志请求所在的行号、消息和行分隔符。
第二个名为STDOUT的附加程序输出到控制台。此附加程序的编码器仅输出后面跟有行分隔符的消息字符串。

Appenders accumulate
<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n</pattern>
    </encoder>
  </appender>

  <logger name="chapters.configuration">
    <appender-ref ref="STDOUT" />
  </logger>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

在这里插入图片描述
日志会重复因为chapters.configuration继承了root有了一个stdout 自己又add了一个
其实这里可以选择不继承 也就是

下面的demo有

Overriding the default cumulative behaviour
<configuration>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>foo.log</file>
    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file : %line] -%kvp- %msg%n</pattern>
    </encoder> 
  </appender>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <logger name="chapters.configuration.Foo" additivity="false">
    <appender-ref ref="FILE" />
  </logger>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

This example, the appender named FILE is attached to the chapters.configuration.Foo logger. Moreover, the chapters.configuration.Foo logger has its additivity flag set to false such that its logging output will be sent to the appender named FILE but not to any appender attached higher in the hierarchy. Other loggers remain oblivious to the additivity setting of the chapters.configuration.Foo logger. Running the MyApp3 application with the additivityFlag.xml configuration file will output results on the console from the chapters.configuration.MyApp3 logger. However, output from the chapters.configuration.Foo logger will appear in the foo.log file and only in that file.
chapters.configuration.Foo 只有file appender 没有stdout

Variable substitution

本文件的早期版本使用了“属性替换”一词,而不是“变量”一词。请考虑这两个术语可以互换,尽管后一个术语表达了更明确的含义。
与许多脚本语言一样,logback配置文件支持变量的定义和替换。变量有一个作用域(见下文)。此外,变量可以在配置文件本身、外部文件、外部资源中定义,甚至可以动态计算和定义。
变量替换可以发生在配置文件中可以指定值的任何位置。变量替换的语法与Unix shell的语法相似。开头 {和结尾}之间的字符串被解释为对属性值的引用。对于属性 a N a m e ,字符串“ {和结尾}之间的字符串被解释为对属性值的引用。对于属性aName,字符串“ {和结尾}之间的字符串被解释为对属性值的引用。对于属性aName,字符串{aName}”将替换为aName属性所持有的值。
HOSTNAME和CONTEXT_NAME变量通常很方便,它们是自动定义的,并且具有上下文范围。考虑到在某些环境中计算主机名可能需要一些时间,它的值会延迟计算(仅在需要时)。此外,HOSTNAME可以直接从配置中进行设置。

Defining variables

<variable><properties>都可以定义变量

demo1
<configuration>

  <variable name="USER_HOME" value="/home/sebastien" />

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${USER_HOME}/myApp.log</file>
    <encoder>
      <pattern>%kvp %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
  </root>
</configuration>
demo2
<configuration>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${USER_HOME}/myApp.log</file>
    <encoder>
      <pattern>%kvp %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
  </root>
</configuration>

java -DUSER_HOME=“/home/sebastien” MyApp2

demo3
<configuration>

  <variable file="src/main/java/chapters/configuration/variables1.properties" />

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
     <file>${USER_HOME}/myApp.log</file>
     <encoder>
       <pattern>%kvp %msg%n</pattern>
     </encoder>
   </appender>

   <root level="debug">
     <appender-ref ref="FILE" />
   </root>
</configuration>

logback-examples/src/main/resources/chapters/configuration/variables1.properties

USER_HOME=/home/sebastien

demo4

You may also reference a resource on the class path instead of a file.

<configuration>

  <variable resource="resource1.properties" />

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
     <file>${USER_HOME}/myApp.log</file>
     <encoder>
       <pattern>%kvp %msg%n</pattern>
     </encoder>
   </appender>

   <root level="debug">
     <appender-ref ref="FILE" />
   </root>
</configuration>

Scopes

A property can be defined for insertion in local scope, in context scope, or in system scope. Local scope is the default. Although it is possible to read variables from the OS environment, it is not possible to write into the OS environment.

LOCAL SCOPE A property with local scope exists from the point of its definition in a configuration file until the end of interpretation/execution of said configuration file. As a corollary, each time a configuration file is parsed and executed, variables in local scope are defined anew.

CONTEXT SCOPE A property with context scope is inserted into the context and lasts as long as the context or until it is cleared. Once defined, a property in context scope is part of the context. As such, it is available in all logging events, including those sent to remote hosts via serialization.

SYSTEM SCOPE A property with system scope is inserted into the JVM’s system properties and lasts as long as the JVM or until it is cleared.

demo1

<configuration>

  <variable scope="context" name="nodeId" value="firstNode" />

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>/opt/${nodeId}/myApp.log</file>
    <encoder>
      <pattern>%kvp %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
  </root>
</configuration>

在上面的示例中,假设nodeId属性是在上下文范围中定义的,那么它将在每个日志记录事件中可用,即使是那些通过序列化发送到远程主机的事件。

Default values for variables

Under certain circumstances, it may be desirable for a variable to have a default value if it is not declared or its value is null. As in the Bash shell, default values can be specified using the “:-” operator. For example, assuming the variable named aName is not defined, “${aName:-golden}” will be interpreted as “golden”.

在某些情况下,如果变量未声明或其值为null,则可能希望该变量具有默认值。与Bashshell一样,可以使用“:-”运算符指定默认值。例如,假设未定义名为aName的变量,“${aName:-golden}”将被解释为“golden”。

Nested variables

Variable nesting is fully supported. Both the name, default-value and value definition of a variable can reference other variables.

完全支持变量嵌套。变量的名称、默认值和值定义都可以引用其他变量。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值