##1.概述
日志服务(Log Service,简称 Log)是针对日志类数据的一站式服务,在阿里巴巴集团经历大量大数据场景锤炼而成。您无需开发就能快捷完成日志数据采集、消费、投递以及查询分析等功能,提升运维、运营效率,建立 DT 时代海量日志处理能力。
##2. Producer Library
阿里官方虽然给出了demo(阿里官方Demo),但是不适用于实际开发,阿里建议开发者使用Producer Library进行日志采集等工作(Producer Library Demo)。LogHub Producer Library 是针对应用程序高并发写LogHub类库,Producer Library 和Consumer Library是对LogHub的读写包装,降低数据收集与消费的门槛。
Producer Library 的优点:
- 提供异步的发送接口,线程安全。
- 可以添加多个Project的配置。
- 可以配置用于发送的网络 I/O 线程数量。
- 可以配置merge成的包的日志数量以及大小。
- 内存使用可控,当内存使用达到用户配置的阈值时,Producer 的 send 接口会阻塞,直到有空闲的内存可用。
##3.应用
在SpringBoot中,使用Java配置方式对LogProducer进行相关配置。
package com.school.config;
import com.aliyun.openservices.log.producer.LogProducer;
import com.aliyun.openservices.log.producer.ProducerConfig;
import com.aliyun.openservices.log.producer.ProjectConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
@Scope("singleton")
public class AliLogConfig {
public static String accessKeyId = "****";
public static String accessKeySecret = "****";
public static String endPoint = "****";
public static String projectName = "****";
@Bean
@ConditionalOnClass(LogProducer.class)
public LogProducer getLogProducer() {
LogProducer producer = new LogProducer(producerConfig());
producer.setProjectConfig(projectConfig());
return producer;
}
@Bean
@ConditionalOnClass(ProducerConfig.class)
public ProducerConfig producerConfig() {
ProducerConfig producerConfig = new ProducerConfig();
//被缓存起来的日志的发送超时时间,如果缓存超时,则会被立即发送,单位是毫秒
producerConfig.packageTimeoutInMS = 1000;
//每个缓存的日志包的大小的上限,不能超过5MB,单位是字节
producerConfig.logsBytesPerPackage = 5 * 1024 * 1024;
//每个缓存的日志包中包含日志数量的最大值,不能超过4096
producerConfig.logsCountPerPackage = 4096;
//单个producer实例可以使用的内存的上限,单位是字节
producerConfig.memPoolSizeInByte = 1000 * 1024 * 1024;
//IO线程池最大线程数量,主要用于发送数据到日志服务
producerConfig.maxIOThreadSizeInPool = 50;
//当使用指定shardhash的方式发送日志时,这个参数需要被设置,否则不需要关心。后端merge线程会将映射到同一个shard的数据merge在一起,而shard关联的是一个hash区间,
//producer在处理时会将用户传入的hash映射成shard关联hash区间的最小值。每一个shard关联的hash区间,producer会定时从loghub拉取,该参数的含义是每隔shardHashUpdateIntervalInMS毫秒,
producerConfig.shardHashUpdateIntervalInMS = 10 * 60 * 1000;
producerConfig.retryTimes = 3;
return producerConfig;
}
@Bean
@ConditionalOnClass(ProjectConfig.class)
public ProjectConfig projectConfig() {
return new ProjectConfig(projectName, endPoint, accessKeyId, accessKeySecret);
}
}
producer 的方法是线程安全的,一般情况一个进程的所有线程共用一个 producer。所以在讲Producer配置为单例,全局唯一。
package com.school.utils;
import com.aliyun.openservices.log.common.LogItem;
import com.aliyun.openservices.log.producer.LogProducer;
import com.school.config.AliLogConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Vector;
@Component
public class AliLogUtil {
private static Logger logger = LoggerFactory.getLogger(AliLogUtil.class);
public static String projectName = "*****";
@Autowired
private AliLogConfig aliLogConfig;
@Value("${aliyun.log_store}")
private String logStore;
public void saveLog(Vector<LogItem> logGroup, String topic, String source) {
final LogProducer logProducer = aliLogConfig.getLogProducer();
// 并发调用 send 发送日志
logger.info("projectName:" + projectName + ", logStore:" + logStore + ", topic:" + topic + ", source:"
+ source);
logger.info("发送日志到" + logStore + ":" + logGroup.get(0).ToJsonString());
logProducer.send(projectName, logStore, topic, source, logGroup, new CallbackLogInfo(projectName, logStore, topic,
null, source, logGroup, logProducer));
// 主动刷新缓存起来的还没有被发送的日志
logProducer.flush();
// 等待数据发送完毕
// Thread.sleep(2 * producerConfig.packageTimeoutInMS);
// 关闭后台 io 线程,close 会将调用时刻内存中缓存的数据发送出去
logProducer.close();
}
}
public class CallbackLogInfo extends ILogCallback {
private static final Logger logger = LoggerFactory.getLogger(CallbackLogInfo.class);
// 保存要发送的数据,当时发生异常时,进行重试
public String project;
public String logstore;
public String topic;
public String shardHash;
public String source;
public Vector<LogItem> items;
public LogProducer producer;
public int retryTimes = 0;
public CallbackLogInfo(String project, String logstore, String topic, String shardHash, String source,
Vector<LogItem> items, LogProducer producer) {
super();
this.project = project;
this.logstore = logstore;
this.topic = topic;
this.shardHash = shardHash;
this.source = source;
this.items = items;
this.producer = producer;
}
public void onCompletion(PutLogsResponse response, LogException e) {
if (e != null) {
// 打印异常
logger.info(e.GetErrorCode() + ", " + e.GetErrorMessage() + ", " + e.GetRequestId());
// 最多重试三次
if (retryTimes++ < 3) {
producer.send(project, logstore, topic, source, shardHash, items, this);
}
} else {
logger.info("send success, request id: " + response.GetRequestId());
}
}
}
在接口中调用方法:
// 保存日志信息
Vector<LogItem> logItems = new Vector<LogItem>();
// 根据学员id查询学员信息
Map<String, Object> logInfo = userMapper.queryStudentLogInfo(smCostOrders.get(0).getStudentId().toString(), schoolId);
if (logInfo != null) {
LogItem logItem = new LogItem();
logItem.PushBack("telephoneCode",String.valueOf(logInfo.get("telephoneCode")));
logItem.PushBack("schoolName",String.valueOf(logInfo.get("schoolName")));
logItem.PushBack("studentName", String.valueOf(logInfo.get("studentName")));
logItem.PushBack("cityName", String.valueOf(logInfo.get("cityName")));
logItem.PushBack("payType", "代缴费支付");
logItem.PushBack("payMoney", String.valueOf(totalFee));
logItem.PushBack("payTime", String.valueOf(gmt_payment));
logItems.add(logItem);
aliLogUtil.saveLog(logItems, "支付", "学员版");
}
这里需要注意的是,pushBack的时候,一定要注意不能设置空值,否则会失败。
至此,阿里云日志服务就可以使用了,如有不当之处,还请指正。