Google Code Prettify

2018年2月28日 星期三

Spring Boot Admin

Spring Boot 廣泛用於微服務,在這種分散式系統,監控是一件很重要的事,Spring Boot 提供了一個名為 Spring Boot Admin 的監控工具,只需要進行一些設定就可以監控,非常方便,說明如下:
  • 建立一個 spring boot admin 的 server 端程式
下面是這個程式的 build.gradle,和一般的 spring boot 沒什麼不同,主要就是加入紅色那幾行,讓 gradle 協助我們載入相關的 jar 檔。spring boot 預設使用的 log 是 logback,但是我習慣用 log4j2,所以加入綠色的那幾行,將 logback 移除並加入 log4j2。
buildscript {
 ext {
  springBootVersion = '1.5.10.RELEASE'
 }
 repositories {
  mavenCentral()
 }
 dependencies {
  classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
 }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'idv.steven'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
 mavenCentral()
}

configurations.all {
      exclude group: 'log4j', module: 'log4j'
      exclude group: 'org.slf4j', module: 'slf4j-log4j12'
      exclude group: 'org.slf4j', module: 'log4j-over-slf4j'
      exclude group: 'ch.qos.logback', module: 'logback-core'
      exclude group: 'ch.qos.logback', module: 'logback-classic'
}

ext {
      springBootAdminVersion = '1.5.7'
      log4j2Version = '2.10.0'
}

dependencies {
      compile('de.codecentric:spring-boot-admin-starter-server')
      compile('de.codecentric:spring-boot-admin-server-ui')
 
      compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: "${log4j2Version}"
      compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: "${log4j2Version}"
      compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: "${log4j2Version}"
 
      testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencyManagement {
      imports {
            mavenBom "de.codecentric:spring-boot-admin-dependencies:${springBootAdminVersion}"
      }
}
  • 設定 port
server.port=8081
embedded tomcat 預設的 port 是 8080,因為我打算用來測試的 client 程式「第一個 spring boot web 程式 (使用 embedded tomcat)」也有用到 embedded tomcat 已經佔去了 8080 port,所以在 application.properties 中加入如上內容設定使用 8081 port。
  • 執行 server
執行上述 server 程式後,打開瀏覽器,打入 http://localhost:8081,可以看到如下畫面,這樣就確定已經正常啟動了!
接下來要來看一下 client 程式怎麼修改,可以讓這個 server 進行監控。
  • 修改 client 程式的 build.gradle
buildscript {
 ext {
  springBootVersion = '1.5.10.RELEASE'
 }
 repositories {
  mavenCentral()
 }
 dependencies {
  classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
 }
}

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'idv.steven'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
      mavenCentral()
}

configurations.all {
      exclude group: 'log4j', module: 'log4j'
      exclude group: 'org.slf4j', module: 'slf4j-log4j12'
      exclude group: 'org.slf4j', module: 'log4j-over-slf4j'
      exclude group: 'ch.qos.logback', module: 'logback-core'
      exclude group: 'ch.qos.logback', module: 'logback-classic'
}

ext {
      springBootAdminVersion = '1.5.7'
      log4j2Version = '2.10.0'
}

dependencies {
      compile('org.springframework.boot:spring-boot-starter-data-jpa') 
      compile('org.springframework.boot:spring-boot-starter-web')
      compile('org.springframework.boot:spring-boot-starter-thymeleaf')
      compile('de.codecentric:spring-boot-admin-starter-client')
 
      compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: "${log4j2Version}"
      compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: "${log4j2Version}"
      compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: "${log4j2Version}"
      
      compile group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.2.1'
      compile group: 'javax.inject', name: 'javax.inject', version: '1'
      compileOnly('org.projectlombok:lombok')
      testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencyManagement {
      imports {
            mavenBom "de.codecentric:spring-boot-admin-dependencies:${springBootAdminVersion}"
      }
}
紅色部份是為了導入 Spring Boot Admin client 而加入的。
  • 修改 application.properties (client)
spring.boot.admin.url=http://localhost:8081
spring.boot.admin.client.name=demo

endpoints.sensitive=false
endpoints.logfile.external-file=d:/tmp/demo.log
在 application.properties 中加入如上設定,前兩行指出 Spring Boot Admin Server 的網址及要顯示在監控畫面的程式名稱; 後兩行是為了將 client 的 log4j2 產生的 log file 送到監控畫面而設定的,這樣萬一監控時發現錯誤,可以立即看 log。
  • 執行 client
執行後,client 會自動連到 server 註冊,然後就會顯示在監控畫面,如上。如果想看更詳細的資料,可以按【Details】,將顯示如下畫面,其中第二項「Log」就是 log4j2 的 log file 內容。



2018年2月27日 星期二

docker logs

在「docker 基本指令」中了解了一些 docker 的基本操作指令,這裡來看一下怎麼觀察 docker 運行的過程產生的 logs,就以 Apache HTTP Server 來當例子好了。
  • 安裝 Apache HTTP Server
安裝的指令如下:
docker run -d -p 80:80 --name httpd httpd
-d: 在背景執行
-p: port 的映射,將 httpd service 80 port 對映到本機的 80 port。
安裝好之後以 docker images 查一下,應該可以看到如下:
  • 查看 docker logs
docker logs -f httpd
-f: 查看 log 尾部的意思,也就是說,這個指令只會顯示 log 最後的幾行,如果有新的 log 產生會顯示新的 log,內容差不多如下。
因為這是個 http server,我的電腦的 IP 是 192.168.0.200,在瀏覽器輸入 http://192.168.0.200 會看到如下畫面,表示服務確實在運行。
同時,因為瀏覽器有對 http server 進行 request,可以在剛剛的畫面看到又新增了 log。
  • log file
上面的指令讓我們可以查看 docker log,而實際上這些 log 存放在那呢? 首先進入 /var/lib/docker/containers 目錄,裡面會有正在運行的 container 的 log,目錄名稱為 container process id,http server 是 aaefcd14c3 那一個 (docker ps -a 可得知)。
進到 http server container 的 log 目錄,可以找到一個 {container id}-json.log 的檔案 (檔名很長的那個),看一下內容,如下,非常類似於前面用指令查看到的 log,但仔細看,是 json 格式。
  • Logging Driver
docker log 儲存的格式如上所示是 json 格式,這是預設,是可以變更的,先執行如下指令:
docker info | grep 'Logging Driver'
docker info 指令可以看到 docker 的一些設定,其中一項 Logging Driver 就是設定 log 儲存格式,預設如下,可以看到設定為 json-file。




2018年2月26日 星期一

Gradle: 編譯 spring boot application

這篇說明如何使用 Gradle 編譯 spring boot application,直接來看個例子。
buildscript {
    ext {
        springBootVersion = '1.5.10.RELEASE'
    }
    repositories {
        maven { url "http://maven.springframework.org/milestone" }
        maven { url "http://repo.maven.apache.org/maven2" }
        maven { url "http://repo1.maven.org/maven2/" }
        maven { url "http://amateras.sourceforge.jp/mvn/" }
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

group = 'idv.steven.crawler'
sourceCompatibility = 1.8
targetCompatibility = 1.8

bootRepackage {
    mainClass = 'idv.steven.crawler.CrawlerStarter'
}

jar {
    baseName = 'my-crawler'
    version =  '1.0.0'  
}

buildDir = 'build'

repositories {
      mavenCentral()
      maven { url "http://maven.springframework.org/milestone" }
      maven { url "http://repo.maven.apache.org/maven2" }
      maven { url "http://repo1.maven.org/maven2/" }
      maven { url "http://amateras.sourceforge.jp/mvn/" }
}

configurations.all {
      exclude group: 'log4j', module: 'log4j'
      exclude group: 'org.slf4j', module: 'slf4j-log4j12'
      exclude group: 'org.slf4j', module: 'log4j-over-slf4j'
      exclude group: 'ch.qos.logback', module: 'logback-core'
      exclude group: 'ch.qos.logback', module: 'logback-classic'
}

dependencies {
      def log4j2Version = '2.10.0'
      def fasterxmlVersion = '2.9.3'
      def hibernateVersion = '5.2.13.Final'
 
      compile fileTree(dir: 'lib', include: ['*.jar'])

      compile("org.springframework.boot:spring-boot-starter-web") {
          exclude module: "spring-boot-starter-tomcat"
      }
      compile('org.springframework.boot:spring-boot-starter-aop')
      compile('org.springframework.boot:spring-boot-starter-batch')
      compile('org.springframework.boot:spring-boot-starter-data-jpa')
      compile('org.springframework.boot:spring-boot-starter-mail')
      compile("org.thymeleaf:thymeleaf-spring4")
      compile("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect")
 
      compile group: 'org.hibernate', name: 'hibernate-core', version: "${hibernateVersion}"
      compile group: 'org.hibernate', name: 'hibernate-entitymanager', version: "${hibernateVersion}"
      compile group: 'org.hibernate', name: 'hibernate-validator', version: '6.0.7.Final'
 
      compile group: 'javax.inject', name: 'javax.inject', version: '1'
      compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: "${log4j2Version}"
      compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: "${log4j2Version}"
      compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: "${log4j2Version}"
      compile group: 'joda-time', name: 'joda-time', version: '2.9.9'
      compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: "${fasterxmlVersion}"
      compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "${fasterxmlVersion}"
      compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: "${fasterxmlVersion}"
      compile group: 'org.json', name: 'json', version: '20180130'
      compile group: 'net.minidev', name: 'json-smart', version: '2.3'
      compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0'
      compile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'
      compile group: 'com.lmax', name: 'disruptor', version: '3.3.7'
 
      compileOnly "org.projectlombok:lombok:1.16.20"
 
      testCompile('org.springframework.boot:spring-boot-starter-test')
      testCompile('org.springframework.batch:spring-batch-test')
}

  • 一開始 buildscript 中紅色部份,設定 spring boot 版本,後面 dependencies (依賴) 的部份引入 spring boot 時,即會引入這個版本。
  • buildscript 的 repositories 指定了好幾個版本庫,一般都只指定 mavenCentral(),但是,有時這個版本庫裡可能沒有我們要的 jar 檔,也可能剛好掛了或被公司防火牆擋了,設定多個比較可以確保可以抓到需要的 jar 檔。
  • apply plugin 我們導入 java 和 org.springframe.boot,這樣我們就可以使用這兩個物件的 method。
  • bootRepackage (黃底) 設定這個 application 的 main 檔在那一個類別裡。
  • jar (綠底) 設定了這個 application 編譯出來的檔名及版號。
  • buildDir (紅底) 指出編譯出來的檔案要放在那個目錄下,不指定的話,預設就會放在 build 目錄下。
  • configurations.all 在這裡可以定義一些 global 的設定,以上例來說,定義了在依賴關係中,如果有依賴這些 jar 檔的,就不要引入,因為 spring boot 預設是的 log 機制是用 logback,有些其它的 framework 預設是用 log4j,但是我希望這個程式用 log4j2,所以在 這些設定排除 logback 及 log4j 的 jar 檔。
  • compile fileTree 是當有一些 jar 檔就在自己的本機,因為我有使用一些公司內部自行開發的共用函式庫的 jar 檔,這些 jar 檔放在 lib 目錄下,如此設定,就會把這些 jar 檔也包進來一起編譯。
  • exclude module (紫底) 是在設定要引入的 jar 檔時,Gradle 會自動將相依的 jar 檔包裝進來,但是,有時有些 jar 檔我們並不需要,就可以用這個方法將它排除! 在 spring boot 中,引入 web 時,預設會包含 embedded tomcat,但是這個程式不是 web 程式,只是會用到 http client,所以將 embedded tomcat 排程。這裡還有個要特別注意的,spring boot 在一開頭已經有設定版號,這些不需寫版號。
  • 有些 jar 檔只有在 compile 階段會用到,這時候就可以用 compileOnly (橘底),lombok 只是在編譯時幫程式員產生些像是 getter、setter method,或是 log 的宣告等,以簡化程式的撰寫,但是產生好讓 Gradle 編譯後,就沒它的事了,runtime 並不會用到,也不會被包入最後產生的 jar 檔裡。
  • testCompile 是用來引入單元測試用的 jar 檔,這些 jar 檔只有開發階段進行單元測試時會用到,也不會被包入最後產生的 jar 檔裡。

2018年2月23日 星期五

Gradle: task

Gradle 中可以有許多 task,透過 task 可以將 build.gradle 中的工作分成細項,讓我們指定要執行的項目,現在來看個例子:
version = '0.1.0-SNAPSHOT'

task first(group: "test", description: "first task") { 
    doLast {
        println "first" 
    }
}

task second { 
    doLast {
        println "second" 
    }
}

task printVersion(dependsOn: [second, first], group: "test", description: "printVersion task") {
    doLast {
        logger.quiet "Version: $version"
    }
}
  • task 的屬性: group、description
如上有三個 task,task first 後面還帶了兩個屬性 - group、description,group 可以用來將 task 編組,description 則可以用來描述 task 的功用,這有什麼好處? 我們可以執行:
gradle tasks --all
這個命令可以列出所有 task,結果如下:
可以看到 first、printVersion 兩個 task 被放在同一組,也有加上 description 的說明,這樣可以方便非 gradle script 的編寫者了解 gradle script 中各個 task 的功用。
  • 使用 dependsOn 定義 task 間的依賴關係
看完說明,執行看看,指令如下:
gradle printVersion
這個指令是指出,只執行 printVersion 這個 task 就好,執行結果如下:
結果三個 task 都有執行,因為在 printVersion 中有個 dependsOn 屬性,指出在執行 printVersion 前要先執行 second 和 first,所以三個都執行了! 要特別注意的是,dependsOn 不保證 task 依賴的順序,就像 gradle script 中寫的是 [second, first],看似要先執行 second,實際上卻先執行 first。
  • -q 即是 quiet
上面的執行方式有點"雜訊",如果我們只想輸出 task 本身輸出的訊息,指令可以如下:
gradle -q printVersion
得到如下結果: