通過PackId機(jī)制關(guān)聯(lián)日志上下文
日志上下文查詢用于指定日志來源(例如,特定的機(jī)器和文件),并查找該來源中某條日志的前后若干條日志,以獲取其上下文信息。在處理大量日志時(shí),可通過為日志添加標(biāo)識符 PackId 將相關(guān)日志進(jìn)行分組。使用 PackId 可以快速且完整地查詢相關(guān)日志組,從而高效定位日志的上下文。本文介紹如何為日志添加PackId。
工作原理
服務(wù)端利用PackId機(jī)制關(guān)聯(lián)日志上下文,PackId格式為上下文前綴-日志組ID
,例如 5FA51423DDB54FDA-1E3
。說明如下:
上下文前綴:由大寫字母和數(shù)字組成,例如
5FA51423DDB54FDA
。相同上下文前綴表示屬于同一日志上下文。日志組ID:由大寫的十六進(jìn)制數(shù)字表示。例如
1E3
。在同一日志上下文內(nèi),日志組ID
是遞增的,例如1E3
與1E4
屬于同一日志上下文的相鄰日志組。
PackId是由客戶端生成的,并在日志寫入請求時(shí)一同發(fā)送到服務(wù)端。具有相同上下文前綴的日志被認(rèn)為屬于同一日志上下文。
自動(dòng)生成PackId
使用Producer SDK寫入的日志:由同一Producer對象實(shí)例發(fā)送的日志數(shù)據(jù)屬于同一上下文,可以直接用于上下文查詢。例如,當(dāng)使用Aliyun Log Java Producer寫入日志數(shù)據(jù)或C SDK寫入日志時(shí),系統(tǒng)會自動(dòng)生成并攜帶PackId作為上下文標(biāo)識。
通過Logtail采集的日志:使用Logtail采集的日志會自動(dòng)生成并攜帶PackId,同一采集對象(如主機(jī)、pod等)上的同一日志文件屬于同一上下文,可以直接用于上下文查詢。
手動(dòng)生成PackId并通過PutLogs接口上傳
參數(shù)說明
PutLogs - 寫入日志接口用于將日志寫入日志服務(wù)。在手動(dòng)生成PackId后,需將該P(yáng)ackId放置在PutLogs請求中的LogGroup的LogTags屬性里,其中Key設(shè)定為__pack_id__
,參數(shù)示例如下:
{
"Topic": "my-topic",
"Source": "127.0.0.1",
"LogTags": [
{
"Key": "__pack_id__",
"Value": "5FA51423DDB54FDA-1"
},
{
"Key": "my_other_tag_key",
"Value": "my_other_tag_value"
}
],
"Logs": [
{
"Time": 1728961415,
"Contents": [
{
"Key": "hello",
"Value": "world"
}
]
}
]
}
示例代碼
Java示例
在pom.xml文件中添加如下依賴。
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>27.0.1-jre</version> </dependency> <dependency> <groupId>com.aliyun.openservices</groupId> <artifactId>aliyun-log</artifactId> <version>0.6.111</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.12</version> </dependency>
使用如下代碼,手動(dòng)生成PackId并通過PutLogs接口上傳。根據(jù)實(shí)際情況替換參數(shù)
project
、logstore
、endpoint
、accessKeyId
和accessKeySecret
。package org.example; import com.aliyun.openservices.log.Client; import com.aliyun.openservices.log.common.LogItem; import com.aliyun.openservices.log.common.TagContent; import com.aliyun.openservices.log.exception.LogException; import com.aliyun.openservices.log.request.PutLogsRequest; import com.google.common.base.Charsets; import com.google.common.hash.Hashing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.List; import java.util.concurrent.atomic.AtomicLong; public class Main { private static final String TAG_PACK_ID = "__pack_id__"; private static final int TOKEN_LEN = 4; public static void main(String[] args) throws LogException { System.out.println("Hello world!"); // 同一個(gè)上下文只需要使用同一個(gè) PackIdGenerator 就可以了 PackIdGenerator generator1 = new PackIdGenerator(); System.out.println(generator1.generateNewPackId()); System.out.println(generator1.generateNewPackId()); System.out.println(generator1.generateNewPackId()); // 不同的上下文用不同的 PackIdGenerator PackIdGenerator generator2 = new PackIdGenerator(); System.out.println(generator2.generateNewPackId()); System.out.println(generator2.generateNewPackId()); // 與日志相配合 String project = "project"; String logstore = "logstore"; String topic = "topic"; String source = "127.0.0.1"; Client client = new Client("endpoint", "accessKeyId", "accessKeySecret"); List<LogItem> logs = new ArrayList<>(); LogItem log = new LogItem(); log.PushBack("hello", "world"); logs.add(log); // 發(fā)送日志請求 PutLogsRequest req = new PutLogsRequest(project, logstore, topic, source, logs); // 把 pack id 放在 tag 列表里 req.SetTags(Arrays.asList(new TagContent(TAG_PACK_ID, generator1.generateNewPackId()))); client.PutLogs(req); // 發(fā)送日志請求 PutLogsRequest req2 = new PutLogsRequest(project, logstore, topic, source, logs); req.SetTags(Arrays.asList(new TagContent(TAG_PACK_ID, generator1.generateNewPackId()))); client.PutLogs(req2); } public static class NetworkUtils { private NetworkUtils() { } public static boolean isIpAddress(final String ipAddress) { if (ipAddress == null || ipAddress.isEmpty()) { return false; } try { final String[] tokens = ipAddress.split("\\."); if (tokens.length != TOKEN_LEN) { return false; } for (String token : tokens) { int i = Integer.parseInt(token); if (i < 0 || i > 255) { return false; } } return true; } catch (Exception ex) { return false; } } public static String getLocalMachineIp() { try { Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); while (networkInterfaces.hasMoreElements()) { NetworkInterface ni = networkInterfaces.nextElement(); if (!ni.isUp()) { continue; } Enumeration<InetAddress> addresses = ni.getInetAddresses(); while (addresses.hasMoreElements()) { final InetAddress address = addresses.nextElement(); if (!address.isLinkLocalAddress() && address.getHostAddress() != null) { String ipAddress = address.getHostAddress(); if ("127.0.0.1".equals(ipAddress)) { continue; } if (isIpAddress(ipAddress)) { return ipAddress; } } } } } catch (SocketException ex) { // swallow it } return null; } } public static class PackIdGenerator { private static final Logger LOGGER = LoggerFactory.getLogger(PackIdGenerator.class); private static final AtomicLong GENERATORID = new AtomicLong(0); private final String packIdPrefix; private final AtomicLong batchId = new AtomicLong(0); public PackIdGenerator() { packIdPrefix = generatePackIdPrefix(GENERATORID.getAndIncrement()).toUpperCase() + "-"; } public String generateNewPackId() { return packIdPrefix + Long.toHexString(batchId.getAndIncrement()).toUpperCase(); } private String generatePackIdPrefix(Long instanceId) { String ip = NetworkUtils.getLocalMachineIp(); if (ip == null) { LOGGER.warn("Failed to get local machine ip, set ip to 127.0.0.1"); ip = "127.0.0.1"; } String name = ManagementFactory.getRuntimeMXBean().getName(); String input = ip + "-" + name + "-" + instanceId; return Hashing.farmHashFingerprint64().hashString(input, Charsets.US_ASCII).toString(); } } }
參數(shù)名
參數(shù)含義
示例值
project
日志服務(wù)的Project名。
test-project
logstore
日志服務(wù)的Logstore名。
test-logstore
endpoint
日志服務(wù)的公網(wǎng)域名,獲取方式請參見服務(wù)接入點(diǎn)。
cn-hangzhou.log.aliyuncs.com
accessKeyId
用戶身份識別ID,獲取方式,請參見創(chuàng)建AccessKey。
LTAI5tK*******
accessKeySecret
用于驗(yàn)證您擁有該AccessKey ID的密碼。獲取方式,請參見創(chuàng)建AccessKey。
3k5PJm*******
Go示例
在命令行執(zhí)行如下命令,安裝Go SDK和protobuf依賴包。
go get -u github.com/aliyun/aliyun-log-go-sdk go get google.golang.org/protobuf
使用如下代碼,手動(dòng)生成PackId并通過PutLogs接口上傳。根據(jù)實(shí)際情況替換參數(shù)
project
、logstore
、endpoint
、accessKeyId
和accessKeySecret
。package main import ( "crypto/md5" "fmt" "os" "sync/atomic" "time" sls "github.com/aliyun/aliyun-log-go-sdk" "google.golang.org/protobuf/proto" ) func main() { // 同一個(gè)上下文只需要使用同一個(gè) PackIdGenerator 就可以了 g1 := NewPackIdGenerator() fmt.Println(g1.Generate()) fmt.Println(g1.Generate()) fmt.Println(g1.Generate()) // 不同的上下文用不同的 PackIdGenerator g2 := NewPackIdGenerator() fmt.Println(g2.Generate()) fmt.Println(g2.Generate()) // 與日志相配合 project := "project" logstore := "logStore" topic := "topic" source := "source" client := sls.CreateNormalInterface("endpoint", "accessKeyId", "accessKeySecret", "") logs := []*sls.Log{ { Time: proto.Uint32(uint32(time.Now().Unix())), Contents: []*sls.LogContent{ { Key: proto.String("hello"), Value: proto.String("world"), }, { Key: proto.String("hi"), Value: proto.String("world"), }, }, }, } // 使用 PostLogStoreLogsV2 接口寫入日志 err := client.PostLogStoreLogsV2(project, logstore, &sls.PostLogStoreLogsRequest{ LogGroup: &sls.LogGroup{ Topic: &topic, Source: &source, Logs: logs, LogTags: []*sls.LogTag{ { Key: proto.String("__pack_id__"), // pack id 加到 tag 列表里 Value: proto.String(g1.Generate()), }, }, }, }) if err != nil { panic(err) } // 使用 PutLogs 接口寫入日志, g1.Generate() 重新生成一個(gè)新的 packid err = client.PutLogs(project, logstore, &sls.LogGroup{ Topic: &topic, Source: &source, Logs: logs, LogTags: []*sls.LogTag{ { Key: proto.String("__pack_id__"), // pack id 加到 tag 列表里 Value: proto.String(g1.Generate()), }, }, }) if err != nil { panic(err) } } type PackIdGenerator struct { prefix string id atomic.Uint64 } func NewPackIdGenerator() *PackIdGenerator { return &PackIdGenerator{ prefix: generatePackIDPrefix(), id: atomic.Uint64{}, } } func (g *PackIdGenerator) Generate() string { return fmt.Sprintf("%s-%X", g.prefix, g.id.Add(1)) } // make context by (hostname, pid, time) func generatePackIDPrefix() string { m := md5.New() m.Write([]byte(time.Now().String())) hostName, _ := os.Hostname() m.Write([]byte(hostName)) m.Write([]byte(fmt.Sprintf("%v", os.Getpid()))) return fmt.Sprintf("%X", m.Sum(nil)) }
參數(shù)名
參數(shù)含義
示例值
project
日志服務(wù)的Project名。
test-project
logstore
日志服務(wù)的Logstore名。
test-logstore
endpoint
日志服務(wù)的公網(wǎng)域名,獲取方式請參見服務(wù)接入點(diǎn)。
cn-hangzhou.log.aliyuncs.com
accessKeyId
用戶身份識別ID,獲取方式,請參見創(chuàng)建AccessKey。
LTAI5tK*******
accessKeySecret
用于驗(yàn)證您擁有該AccessKey ID的密碼。獲取方式,請參見創(chuàng)建AccessKey。
3k5PJm*******
查看日志上下文信息
在目標(biāo)Project頁面,單擊目標(biāo)Logstore,參考下圖,在
頁簽下,找到目標(biāo)日志,單擊圖標(biāo)。說明每次通過PutLogs - 寫入日志提交的日志中,具有相同PackId上下文前綴的日志被視為屬于同一日志上下文。日志服務(wù)內(nèi)部已根據(jù)PackId進(jìn)行分類,單擊上下文瀏覽功能即可查看指定日志的上下文信息,即顯示具有同一上下文前綴的不同日志組,其中高亮代表目標(biāo)日志。
使用鼠標(biāo)在當(dāng)前頁面上下滾動(dòng)查看指定日志的上下文信息。
相關(guān)文檔
在控制臺中查看指定日志的上下文信息,詳細(xì)信息請參見上下文查詢。
使用SDK或API方式將日志數(shù)據(jù)寫入到日志服務(wù),詳細(xì)信息請參見使用Aliyun Log Java Producer寫入日志數(shù)據(jù)、C SDK或PutLogs - 寫入日志。