Programming

basic programming

Dynamic Logback Log Level in Spring Boot

There are times where we want to create a dynamic configuration for our logging level. Lets say for most of cases, logger with level INFO is sufficient enough, but we want a much more detail logger such as DEBUG or TRACE for debugging purpose.

We can achieve that condition by using a dynamic configuration in Logback. This is how we do it, we’ll start with using a specific logging library called Logback.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>springboot-and-logback-xml</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.4</version>
        <relativePath/>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

And create a file called logback.xml,

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

    <property name="APP_LOG_LEVEL" value="${APP_LOG_LEVEL:-INFO}" />
    <property name="ROOT_LOG_LEVEL" value="${ROOT_LOG_LEVEL:-WARN}" />
    
    <appender name="CONSOLE"
              class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <logger name="com.edw" level="${APP_LOG_LEVEL}" additivity="false">
        <appender-ref ref="CONSOLE" />
    </logger>

    <root level="${ROOT_LOG_LEVEL}">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

We can test by using below Java code where we create two different logging line with different log level,

package com.edw.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
public class IndexController {

    private static final Logger logger = LoggerFactory.getLogger(IndexController.class);

    @GetMapping("/")
    public HashMap getIndexPage() {
        logger.debug("we are at index page using {}", "DEBUG");
        logger.info("we are at index page using {}", "INFO");

        return new HashMap() {{
            put("hello", "world");
        }};
    }
}

We can run the project using default command and see default log outputs

$  java -jar springboot-and-logback-xml-1.0-SNAPSHOT.jar

  .   ____          _            __ _ _    
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \   
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \  
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) ) 
  '  |____| .__|_| |_|_| |_\__, | / / / /  
 =========|_|==============|___/=/_/_/_/   
 :: Spring Boot ::                (v3.0.4) 

14:12:49.321 [main] INFO  com.edw.Main - Starting Main v1.0-SNAPSHOT using Java 17.0.6 with PID 13480 
14:12:49.326 [main] INFO  com.edw.Main - No active profile set, falling back to 1 default profile: "default" 
14:12:51.122 [main] INFO  com.edw.Main - Started Main in 2.283 seconds (process running for 2.983) 
14:13:02.489 [http-nio-8080-exec-1] INFO  com.edw.controller.IndexController - we are at index page using INFO 

However for a much more detail outputs, we can use below command with configured “APP_LOG_LEVEL” and “ROOT_LOG_LEVEL”

$ java -DAPP_LOG_LEVEL=DEBUG -DROOT_LOG_LEVEL=INFO -jar springboot-and-logback-xml-1.0-SNAPSHOT.jar

  .   ____          _            __ _ _    
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \   
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \  
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) ) 
  '  |____| .__|_| |_|_| |_\__, | / / / /  
 =========|_|==============|___/=/_/_/_/   
 :: Spring Boot ::                (v3.0.4) 
 
14:15:40.353 [main] INFO  com.edw.Main - Starting Main v1.0-SNAPSHOT using Java 17.0.6 with PID 9004 
14:15:40.356 [main] DEBUG com.edw.Main - Running with Spring Boot v3.0.4, Spring v6.0.6                      
14:15:40.357 [main] INFO  com.edw.Main - No active profile set, falling back to 1 default profile: "default" 
14:15:41.774 [main] INFO  o.s.b.w.e.tomcat.TomcatWebServer - Tomcat initialized with port(s): 8080 (http) 
14:15:41.788 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8080"] 
14:15:41.789 [main] INFO  o.a.catalina.core.StandardService - Starting service [Tomcat]
14:15:41.789 [main] INFO  o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.5]
14:15:41.903 [main] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext 
14:15:41.905 [main] INFO  o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1449 ms 
14:15:42.365 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"] 
14:15:42.404 [main] INFO  o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path '' 
14:15:42.425 [main] INFO  com.edw.Main - Started Main in 2.604 seconds (process running for 3.422) 
14:17:29.454 [http-nio-8080-exec-1] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet' 
14:17:29.455 [http-nio-8080-exec-1] INFO  o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet' 
14:17:29.456 [http-nio-8080-exec-1] INFO  o.s.web.servlet.DispatcherServlet - Completed initialization in 1 ms
14:17:29.490 [http-nio-8080-exec-1] DEBUG com.edw.controller.IndexController - we are at index page using DEBUG 
14:17:29.492 [http-nio-8080-exec-1] INFO  com.edw.controller.IndexController - we are at index page using INFO 

For Kubernetes delployments, we can also use below environment variables

  env:
	- name: APP_LOG_LEVEL
	  value: DEBUG
	- name: ROOT_LOG_LEVEL
	  value: INFO

Creating a Symbolic Link in Windows 10 and Push it on Git Repository

Sometimes we want to create a symlink on our code and push it to Git repository. For Linux or MacOS system, it is quite straightforward. However things are different in Windows 10, which we can use below command

$ mklink  new.txt old.txt

Sample git repo with symlink file can be found in below URL,

https://github.com/edwin/symlinks-on-windows-and-git

Setting Hostname as Custom Log Folder for JBoss EAP 7.4

Basically we can create a folder to store JBoss EAP logs with hostname as its name. We can leverage “jboss.node.name” variable for this.

            <periodic-rotating-file-handler name="FILE" autoflush="true">
                <formatter>
                    <named-formatter name="PATTERN"/>
                </formatter>

				<file path="/log/${jboss.node.name}/server.log"/>
				
                <suffix value=".yyyy-MM-dd"/>
                <append value="true"/>
            </periodic-rotating-file-handler>

Setting Up Custom Maven Repository with a Relative Folder

There are times where we want to build our application while pointing our library to a specific custom folder, which is having a relative folder location to current project Java folder.

For example, i have a Java project and a Libs folder with structure like below

libs/
+--- org
|   +--- postgresql
|   |   +--- postgresql
|   |   |   +--- random.version
|   |   |   |   +--- postgresql-random.version.jar

java_project/
+--- pom.xml
+--- src
|   +--- main
|   |   +--- java

We can build our java_project by refering to libs folder by using this configuration on our pom.xml

    <repositories>
        <repository>
            <id>local-repo</id>
            <name>Local Repository</name>
            <url>file://${project.basedir}/../libs</url>
        </repository>
    </repositories>

And refer it

	<dependency>
		<groupId>org.postgresql</groupId>
		<artifactId>postgresql</artifactId>
		<version>random.version</version>
	</dependency>

Running maven build command will display the complete build log where library is coming from our relative folder location,

$ mvn clean package

.......

Downloading from local-repo: file:///source/java_project/../libs/org/postgresql/postgresql/random.version/postgresql-random.version.jar
[WARNING] Could not validate integrity of download from file:///source/java_project/../libs/org/postgresql/postgresql/random.version/postgresql-random.version.jar: Checksum validation failed, no checksums available
[WARNING] Checksum validation failed, no checksums available from local-repo for file:///source/java_project/../libs/org/postgresql/postgresql/random.version/postgresql-random.version.jar
Downloaded from local-repo: file:///source/java_project/../libs/org/postgresql/postgresql/random.version/postgresql-random.version.jar (1.0 MB at 7.0 MB/s)

Distributed Tracing with Spring Boot, Infinispan, and Jaeger

Distributed Tracing with Spring Boot, Infinispan, and Jaeger

Infinispan 14, or Datagrid 8.4, has the capability to implement a distributed tracing which will make distributed tracing easier and providing a better end-to-end view.

The concept of distributed tracing perhaps looks like this,

For this example, we are going to use Spring Boot 3 and OpenTelemetry for client side, and Jaeger all-in-one on for monitoring. So lets start with a simple pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>spring-boot-infinispan-and-jaeger</artifactId>
    <version>1.0</version>

    <name>Spring Boot 3 with OpenTelemetry and Jaeger</name>
    <description>Spring Boot testing app with opentelemetry</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <version.infinispan>14.0.2.Final</version.infinispan>
        <version.protostream>4.6.2.Final</version.protostream>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.infinispan</groupId>
                <artifactId>infinispan-bom</artifactId>
                <version>${version.infinispan}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- tracing -->
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-tracing-bridge-otel</artifactId>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-exporter-otlp</artifactId>
        </dependency>

        <!-- infinispan -->
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring-boot-starter-remote</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-query</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-remote-query-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-api</artifactId>
        </dependency>

        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring-boot-starter-embedded</artifactId>
        </dependency>

        <dependency>
            <groupId>org.infinispan.protostream</groupId>
            <artifactId>protostream-processor</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-client-hotrod</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
            <version>1.1</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Next is some Java classes,

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}
@RestController
public class IndexController {

    private static final Logger logger = LoggerFactory.getLogger(IndexController.class);

    @Autowired
    private IndexService indexService;

    @GetMapping("/")
    public ResponseEntity path1() {

        logger.info("request at / ");
        indexService.createData();
        return ResponseEntity.ok().build();
    }

}
@Service
public class IndexService {

    @Autowired
    private RemoteCacheManager cacheManager;

    @Autowired
    private Tracer tracer;

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    public void createData() {
        logger.info("starting ====================");

        // starting span
        Span span = tracer.spanBuilder()
                .name("cache-add")
                .tag("cache", "balance")
                .kind(Span.Kind.CLIENT).start();

        final RemoteCache cache = cacheManager.getCache("balance");

        // generate cache key
        String key = UUID.randomUUID().toString();

        // put it
        cache.put(key, Math.random());

        // get it -- somehow this is not monitored in Jaeger
        Double value = Double.parseDouble((String)cache.get(key));

        // and remove it
        cache.remove(key);

        span.end();

        logger.info("done processing {} - {} ====================", key, value);
    }
}
@Configuration
public class OtlpConfiguration {

    @Bean
    public OtlpHttpSpanExporter otlpHttpSpanExporter() {
        return OtlpHttpSpanExporter.builder()
                .build();
    }

}
@Configuration
public class InfinispanConfiguration {
    @Bean
    public RemoteCacheManager remoteCacheManager() {
        return new RemoteCacheManager(
                new org.infinispan.client.hotrod.configuration.ConfigurationBuilder()
                        .addServers("127.0.0.1:11222")
                        .security().authentication().username("admin").password("password")
                        .clientIntelligence(ClientIntelligence.HASH_DISTRIBUTION_AWARE)
                        .marshaller(ProtoStreamMarshaller.class)
                        .build());
    }
}
server.port=8080
spring.application.name=app-testing

management.tracing.sampling.probability=1.0
management.otlp.metrics.export.url=http://localhost:4318/v1/traces
management.otlp.metrics.export.step=5s

logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]

Once we have all Java classes ready, we’ll continue with setting Jaeger server up. We’ll need

docker run -d \ 
        -e COLLECTOR_OTLP_ENABLED=true \ 
        -p 16686:16686 \ 
        -p 4317:4317 \
        -p 4318:4318 \ 
        jaegertracing/all-in-one:1.40

Next is setting up our Infinispan cache,

<distributed-cache name="balance" mode="SYNC" remote-timeout="30000" statistics="true">
    <encoding media-type="text/plain"/>
    <locking concurrency-level="1000" isolation="READ_COMMITTED" acquire-timeout="60000" striping="false"/>
    <transaction mode="NON_XA" auto-commit="true" stop-timeout="30000" locking="PESSIMISTIC" reaper-interval="30000" complete-timeout="30000" notifications="true"/>
    <state-transfer timeout="30000"/>
</distributed-cache>

And dont forget to put this Java runtime variables on our Infinispan,

JAVA_OPTS="$JAVA_OPTS -Dinfinispan.tracing.enabled=true -Dotel.service.name=infinispan-server
            -Dotel.exporter.otlp.endpoint=http://localhost:4317 -Dotel.metrics.exporter=none"

We can run this app, and see the result on our Jaeger dashboard.

And this is the detail traces within one same request,

Code for this article can be found on below Github link

https://github.com/edwin/spring-boot-infinispan-and-jaeger

Have fun.