意义:规范的数据接入能大大减少后续的维护及使用代价
规定:
公司的一般数据源包括:日志文件,业务mysql,kafka中数据
接入的数据分为实时接入和天级接入:
flume->kafka->spark->hdfs file
注意事项:日志非准确跨天问题。(我们采用扫描最新一个日志文件没前一天的数据就开始计算)
maxwell->kafka->spark->hbase
hbase只提供简单rowkey 点查询,后续可能会考虑clickhouse
1.现状:
线上日志保留时间不统一。有的只保留当天,第二天出现问题无从追查。
日志格式不统一,有的用tab分隔的,有的json。
2.目标:
便于程序调试
线上问题追查
方便后续解析
计划统一后续日志的打印规范,现征求意见稿如下:
有任何建议及时邮件或者在此留言。
3.应用日志
3.1.日志打印规范:
日志文件:
一小时一个(单个文件最好不要超过1G,否则在线问题追查时,grep太浪费cpu)
保留至少3天(第二天发现第一天的统计报表问题可以返回现场追查)
日志格式(需要入hive和进行spike处理的日志都必须为json格式):
日志时间 日志级别 进程名称 行号 json 的dict结构。(方便进行问题追查)
dict允许嵌套,dict的key的命名方式只能包含【大小写26个英文字符、数字、下划线】。(其它字符保留给配置文件的元字符)
遇到list时不再往里解析作为一个整体处理
3.2.示例
2018-09-10 20:57:16INFO main com.soul.dw.syncmysqlhbase.Utils.main(Utils.java:43){"userID":"123344", "detail":"im a good boy!!!!", "dict":{"dict_content1":"content1","dict_content1":"content"}, "list":["fdasdfs", }
可能的问题:如果json的内容中有换行符号,会导致被当做多行日志处理,格式不符合,解析失败。(现在也有这个问题,非此次引入。要解决只能用二进制编码流方式收集日志,比如用kafkaclient直接写入kafka)
3.3.实现
Log4j java文件和配置()
Log4j不支持按时间切割的,保留固定文件数据自动删除,需要自己实现。抄袭的网上解决方案,已通过测试。
对应的log4j配置:(MyDailyRollingFileAppender,MaxFileSize为保存日志的份数)
logback配置(待补充,谁熟悉可以帮忙解决一下)
4.Nginx日志打印规范
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
log4j 按时间分割,自动删除类&及配置
注意事项:必须建立包 org.apache.log4j,在其目录下实现此类
MyDailyRollingFileAppender.java
package org.apache.log4j;
/**
* copy form https://www.cnblogs.com/rembau/p/5201001.html
*@ClassName MyDailyRollingFileAppender
*@Description TODO
**/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.text.ParseException;
import java.util.*;
public class MyDailyRollingFileAppender extends DailyRollingFileAppender {
private static Logger logger = LoggerFactory.getLogger(MyDailyRollingFileAppender.class);
private int maxFileSize = 60;
void rollOver() throws IOException {
super.rollOver();
logger.debug("保留文件数量" + maxFileSize + ",日志文件名称为:" + fileName);
List<File> fileList = getAllLogs();
sortFiles(fileList);
logger.debug(fileList.toString());
deleteOvermuch(fileList);
}
/**
* 删除过多的文件
* @param fileList 所有日志文件
*/
private void deleteOvermuch(List<File> fileList) {
if (fileList.size() > maxFileSize) {
for (int i = 0;i < fileList.size() - maxFileSize;i++) {
fileList.get(i).delete();
logger.debug("删除日志" + fileList.get(i));
}
}
}
/**
* 根据文件名称上的特定格式的时间排序日志文件
* @param fileList
*/
private void sortFiles(List<File> fileList) {
Collections.sort(fileList, new Comparator<File>() {
public int compare(File o1, File o2) {
try {
if (getDateStr(o1).isEmpty()) {
return 1;
}
Date date1 = sdf.parse(getDateStr(o1));
if (getDateStr(o2).isEmpty()) {
return -1;
}
Date date2 = sdf.parse(getDateStr(o2));
if (date1.getTime() > date2.getTime()) {
return 1;
} else if (date1.getTime() < date2.getTime()) {
return -1;
}
} catch (ParseException e) {
logger.error("", e);
}
return 0;
}
});
}
private String getDateStr(File file) {
if (file == null) {
return "null";
}
return file.getName().replaceAll(new File(fileName).getName(), "");
}
/**
* 获取所有日志文件,只有文件名符合DatePattern格式的才为日志文件
* @return
*/
private List<File> getAllLogs() {
final File file = new File(fileName);
File logPath = file.getParentFile();
if (logPath == null) {
logPath = new File(".");
}
File files[] = logPath.listFiles(new FileFilter() {
public boolean accept(File pathname) {
try {
if (getDateStr(pathname).isEmpty()) {
return true;
}
sdf.parse(getDateStr(pathname));
return true;
} catch (ParseException e) {
logger.error("", e);
return false;
}
}
});
return Arrays.asList(files);
}
public int getMaxFileSize() {
return maxFileSize;
}
public void setMaxFileSize(int maxFileSize) {
this.maxFileSize = maxFileSize;
}
}
log4j.properties
# https://www.cnblogs.com/rembau/p/5201001.html
### set log levels ###
log4j.rootLogger = debug, stdout, file1, file2
### 输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%p]%-d{yyyy-MM-dd HH:mm:ss} %t %l %m%n
### 输出到日志文件 ###
log4j.appender.file2 = org.apache.log4j.MyDailyRollingFileAppender
log4j.appender.file2.File = /tmp/test.log
log4j.appender.file2.Append = true
log4j.appender.file2.Threshold = DEBUG
log4j.appender.file2.MaxFileSize=72
log4j.appender.file2.DatePattern = '.'yyyy-MM-dd-HH
log4j.appender.file2.layout = org.apache.log4j.PatternLayout
log4j.appender.file2.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} %p %t %l %m%n
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property resource="log.properties" />
<!-- console -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}][%level]%class %m%n</pattern>
</encoder>
</appender>
<!-- info 只打印info日志-->
<appender name="systemFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.dir}/tagging-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>N</maxHistory> <!-- 保存N+1天日志,大于N+1前的日志文件会被自动删除 -->
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}][%level]%class %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- error -->
<appender name="errorFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.dir}/tagging-error.%d{yyyy-MM-dd-HH}.log</fileNamePattern>
<maxHistory>1</maxHistory>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}][%level]%class %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="systemFile" />
<appender-ref ref="errorFile" />
</root>
</configuration>
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。