本篇和你們分享的是自定義log4j的appender,用es來記錄日誌而且經過kibana瀏覽es記錄;就目前互聯網或者一些中大型公司一般會用到第三方組合elk,其主要用寫數據到es中,而後經過可視化工具kibana來作直觀數據查看和統計;本篇內容節點以下:java
對於愛研究第三方服務的程序員來講docker是很好的助手,可以快速搭建一套簡易的使用環境;docker啓動es鏡像具體很少說了看這裏docker快速搭建幾個經常使用的第三方服務,值得注意的是這裏我定義了es的集羣名稱,經過以下命令進入容器中改了配置文件(固然可直接經過命令啓動時傳遞參數):node
1 docker exec -it eae7731bb6a1 /bin/bash
而後進入到 /usr/share/elasticsearch/config 並打開elasticsearch.yml配置文件修改:程序員
1 #集羣名稱 2 cluster.name: "shenniu_elasticsearch" 3 #本節點名稱 4 node.name: master 5 #是否master節點 6 node.master: true 7 #是否存儲數據 8 node.data: true 9 #head插件設置 10 http.cors.enabled: true 11 http.cors.allow-origin: "*" 12 http.port: 9200 13 transport.tcp.port: 9300 14 #能夠訪問的ip 15 network.bind_host: 0.0.0.0
這裏定義集羣名爲:shenniu_elasticsearchdocker
如上啓動了es後,咱們爲了直觀的看到es中信息,這裏用到了es header工具(固然沒必要須);只要docker啓動其鏡像後,咱們可以在上面輸入咋們的es地址,以此來檢測es集羣是否開啓並瀏覽相關索引信息,es header默認端口9100:數據庫
一般搭配es的是kibana(可視化工具),用來查看es的數據和作一些統計(如數量統計,按列聚合統計等),這裏經過docker run啓動kibana鏡像後,咱們還須要讓其關聯上es才行,一樣經過docker exec去修改裏面配置信息,主要在裏面配置es地址:apache
1 docker exec -it 67a0ef871ef7 /bin/bash 2 cd etc/ 3 cd kibana/ 4 vim kibana.yml
配置內容修改如:json
1 server.host: '0.0.0.0' 2 elasticsearch.url: 'http://192.168.181.7:9200' #es地址
如上操做完後,打開kibana地址 http://192.168.181.7:5601/app/kibana ,可以看到讓咋們配置es索引查詢規則的界面,若是es地址down掉或者配置不對,kibana會停留在red界面,讓咱們正確配置:vim
java往es中寫數據,能夠使用官網推薦的 org.elasticsearch.client 包(注意版本問題),我這裏es是5.6版本對應的rest-high-leve-client最好也引入5.6版本的,以下pom信息:bash
1 <dependency> 2 <groupId>log4j</groupId> 3 <artifactId>log4j</artifactId> 4 <version>1.2.17</version> 5 </dependency> 6 <dependency> 7 <groupId>org.elasticsearch.client</groupId> 8 <artifactId>elasticsearch-rest-high-level-client</artifactId> 9 <version>5.6.16</version> 10 </dependency> 11 <dependency> 12 <groupId>com.alibaba</groupId> 13 <artifactId>fastjson</artifactId> 14 <version>1.2.56</version> 15 <scope>compile</scope> 16 </dependency>
首先要明確用代碼操做es(或其餘第三方服務),每每都需ip(域名)+端口,這裏個人配置信息:服務器
1 #es鏈接串 ','分割 2 es.links=http://192.168.181.7:9200,http://localhost:9200 3 es.indexName=eslog_shenniu003
而後有以下封裝代碼:
1 public class EsRestHighLevelClient { 2 3 /** 4 * new HttpHost("192.168.181.44", 9200, "http") 5 */ 6 private HttpHost[] hosts; 7 private String index; 8 private String type; 9 private String id; 10 11 public EsRestHighLevelClient(String index, String type, String id, HttpHost[] hosts) { 12 this.hosts = hosts; 13 this.index = index; 14 this.type = type; 15 this.id = id; 16 } 17 18 /** 19 * @param index 20 * @param type 21 * @param hosts 22 */ 23 public EsRestHighLevelClient(String index, String type, String... hosts) { 24 this.hosts = IpHelper.getHostArrByStr(hosts); 25 this.index = index; 26 this.type = type; 27 } 28 29 public RestHighLevelClient client() { 30 Assert.requireNonEmpty(this.hosts, "無效的es鏈接"); 31 32 RestHighLevelClient client = new RestHighLevelClient( 33 RestClient.builder(this.hosts).build() 34 ); 35 return client; 36 } 37 38 public IndexRequest indexRequest() { 39 return new IndexRequest(this.index, this.type, this.id); 40 } 41 42 public RestStatus createIndex(Map<String, Object> map) throws IOException { 43 return client(). 44 index(this.indexRequest().source(map)). 45 status(); 46 } 47 }
這裏還涉及到了一個IpHelper輔助類,主要用來拆分多個ip信息參數,裏面涉及到正則匹配方式:
1 public class IpHelper { 2 3 private static final String strHosts = "(?<h>[^:]+)://(?<ip>[^:]+):(?<port>[^/|,]+)"; 4 private static final Pattern hostPattern = Pattern.compile(strHosts); 5 6 public static Optional<String> getHostIp() { 7 try { 8 return Optional.ofNullable(InetAddress.getLocalHost().getHostAddress()); 9 } catch (UnknownHostException e) { 10 e.printStackTrace(); 11 } 12 return Optional.empty(); 13 } 14 15 public static Optional<String> getHostName() { 16 try { 17 return Optional.ofNullable(InetAddress.getLocalHost().getHostName()); 18 } catch (UnknownHostException e) { 19 e.printStackTrace(); 20 } 21 return Optional.empty(); 22 } 23 24 /** 25 * strHosts:"http://192.168.0.1:9200","http://192.168.0.1:9200","http://192.168.0.1:9200" 26 * 27 * @return 28 */ 29 public static List<HttpHost> getHostsByStr(String... strHosts) { 30 List<HttpHost> hosts = new ArrayList<>(); 31 for (int i = 0; i < strHosts.length; i++) { 32 String[] hostArr = strHosts[i].split(","); 33 for (String strHost : hostArr) { 34 Matcher matcher = hostPattern.matcher(strHost); 35 if (matcher.find()) { 36 String http = matcher.group("h"); 37 String ip = matcher.group("ip"); 38 String port = matcher.group("port"); 39 40 if (Strings.isEmpty(http) || Strings.isEmpty(ip) || Strings.isEmpty(port)) { 41 continue; 42 } 43 hosts.add(new HttpHost(ip, Integer.valueOf(port), http)); 44 } 45 } 46 } 47 return hosts; 48 } 49 50 public static HttpHost[] getHostArrByStr(String... strHosts) { 51 List<HttpHost> list = getHostsByStr(strHosts); 52 return Arrays.copyOf(list.toArray(), list.size(), HttpHost[].class); 53 } 54 }
對於日誌來講log4j是大衆化的,有不少語言也在用這種方式來記錄,使用它至關於一種共識;它提供了很好的擴展,很方便達到把日誌記錄到數據庫,文本獲取其餘自定義代碼方式中;定義一個EsAppend類,繼承AppenderSkeleton類,代碼上咱們要作的僅僅重寫以下方法便可:
本期咋們實現的步驟是:
具體實現代碼以下,可按照上面步驟分析:
1 public class EsAppend extends AppenderSkeleton { 2 3 //es客戶端 4 private static EsRestHighLevelClient esClient; 5 //es配置文件名 6 private String confName; 7 8 private ExecutorService executorService = Executors.newFixedThreadPool(10); 9 10 protected void append(LoggingEvent loggingEvent) { 11 if (this.isAsSevereAsThreshold(loggingEvent.getLevel())) { 12 executorService.execute(new EsAppendTask(loggingEvent, this.layout)); 13 // new EsAppendTask(loggingEvent, this.layout).run(); 14 } 15 } 16 17 public void close() { 18 this.closed = true; 19 } 20 21 public boolean requiresLayout() { 22 return false; 23 } 24 25 @Override 26 public void activateOptions() { 27 super.activateOptions(); 28 try { 29 System.out.println("初始化 - EsAppend..."); 30 31 if (this.getConfName() == null || this.getConfName().isEmpty()) { 32 this.setConfName("eslog.properties"); 33 } 34 PropertiesHelper propertiesHelper = new PropertiesHelper(this.getConfName()); 35 //es hosts 36 String strHosts = propertiesHelper.getProperty("es.links", "http://127.0.0.1:9200"); 37 //es日誌索引 38 String esLogIndex = propertiesHelper.getProperty("es.indexName", "eslog"); 39 esClient = new EsRestHighLevelClient(esLogIndex, "docs", strHosts); 40 41 System.out.println("初始化完成 - EsAppend"); 42 } catch (Exception ex) { 43 System.out.println("初始化失敗- EsAppend"); 44 ex.printStackTrace(); 45 } 46 } 47 48 public String getConfName() { 49 return confName; 50 } 51 52 public void setConfName(String confName) { 53 this.confName = confName; 54 } 55 56 /** 57 * runable寫es 58 */ 59 class EsAppendTask implements Runnable { 60 private HashMap<String, Object> map; 61 62 public EsAppendTask(LoggingEvent loggingEvent, Layout layout) { 63 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd\'T\'HH:mm:ss.SSSZ"); 64 map = new HashMap<String, Object>() { 65 { 66 put("timeStamp",df.format(new Date())); 67 put("serverIp", IpHelper.getHostIp().get()); 68 put("hostname", IpHelper.getHostName().get()); 69 put("level", loggingEvent.getLevel().toString()); 70 71 put("className", loggingEvent.getLocationInformation().getClassName()); 72 put("methodName", loggingEvent.getLocationInformation().getMethodName()); 73 put("data", loggingEvent.getMessage()); 74 75 if (loggingEvent.getThrowableInformation() != null && !CollectionUtils.isEmpty(loggingEvent.getThrowableInformation().getThrowableStrRep())) { 76 put("exception", String.join(";", loggingEvent.getThrowableInformation().getThrowableStrRep())); 77 } else { 78 put("exception", ""); 79 } 80 } 81 }; 82 } 83 84 @Override 85 public void run() { 86 try { 87 EsAppend.esClient.createIndex(map); 88 } catch (IOException e) { 89 e.printStackTrace(); 90 } 91 } 92 } 93 }
如上代碼有一些自定義屬性如confName,這個對應log4j.properties文件中自定義的confName屬性,也就是說代碼中confName和配置文件中的節點對應,能夠直接get獲取值;以下log4j配置信息:
1 # Set root logger level to DEBUG and its only appender to A1. 2 log4j.rootLogger=DEBUG,esAppend 3 # A1 is set to be a ConsoleAppender. 4 log4j.appender.esAppend=log.EsAppend 5 #自定義es配置文件 6 log4j.appender.esAppend.confName=eslog.properties 7 8 # A1 uses PatternLayout. 9 #log4j.appender.esAppend.layout=org.apache.log4j.PatternLayout 10 #log4j.appender.esAppend.layout
上面PatternLayout配置是註釋的,由於對於我寫es來講沒啥用處,不作格式化處理因此能夠直接忽略;
下面列出擴展append時須要注意的地方:
有了上面步驟後,咱們來到測試環節,建一個測試接口,而且請求插入一些數據:
1 static Logger logger = Logger.getLogger(TestController.class); 2 3 @GetMapping("/hello/{nickname}") 4 public String getHello(@PathVariable String nickname) { 5 String str = String.format("你好,%s", nickname); 6 logger.debug(str); 7 logger.info(str); 8 logger.error(str); 9 return str; 10 }
當咱們請求接口 http://localhost:4020/hello/神牛003 一次後,經過es header查看內容以下:
這種方式不怎麼直觀,能夠經過kibana來查看,以下先配置kibana使用的索引:
最後經過Discover界面搜索相關日誌信息: