系統可觀測性(5)OpenTelemetry基礎概念
Author: Once Day Date: 2025年3月12日
一位熱衷于Linux學習和開發的菜鳥,試圖譜寫一場冒險之旅,也許終點只是一場白日夢…
漫漫長路,有人對你微笑過嘛…
本文檔翻譯整理自《OpenTelemetry Docs》,僅用于學習和交流。
全系列文章可參考專欄: 十年代碼訓練_Once-Day的博客-CSDN博客
參考文章:
- What Is Observability? - OBSERVE
- What is OpenTelemetry? - OpenTelemetry
- 分布式系統可觀測性(Observability)系列
- Metrics, tracing, and logging - Peter Bourgon
文章目錄
- 系統可觀測性(5)OpenTelemetry基礎概念
- 1 OpenTelemetry項目
- 2 可觀測性概念
- 3 上下文傳播(Context propagation)
- 4 追蹤(Traces)
- 5. 指標(Metrics)
- 6. 日志(Logs)
- 7. 關聯數據(Baggage)
- 8. 檢測方式
- 9. 組件
- 10. 采樣
1 OpenTelemetry項目
OpenTelemetry是一個開源的、獨立于供應商的遙測數據收集和管理框架,旨在幫助實現系統的可觀測性。它為創建和管理追蹤、指標和日志等遙測數據提供了一套標準化的工具和API。
OpenTelemetry是由OpenTracing和OpenCensus這兩個項目合并而成的,目標是成為可觀測性領域的標準。
OpenTelemetry與供應商和工具無關,可以與多種開源和商業的可觀測性后端兼容,如Jaeger、Prometheus等,避免了供應商鎖定。OpenTelemetry關注遙測數據的生成、收集、管理和輸出,讓用戶能夠輕松地監測應用程序或系統,而無需考慮其語言、基礎設施或運行時環境。遙測數據的存儲和可視化則交由其他工具處理。
OpenTelemetry主要由以下幾部分組成:
- 一套規范,定義了所有組件的標準。
- 一個標準協議,定義了遙測數據的格式。
- 語義約定,定義了常見遙測數據類型的標準命名方案。
- API,定義了如何生成遙測數據。
- SDK,實現了規范、API和遙測數據的輸出。
- 工具生態系統,為常見庫和框架提供了檢測功能。
- 自動檢測組件,無需更改代碼即可生成遙測數據。
- OpenTelemetry Collector,一個接收、處理和輸出遙測數據的代理。
OpenTelemetry的一大特點是高度可擴展。例如,可以為Collector添加自定義數據源的接收器,為SDK加載自定義檢測庫,為特定用例定制SDK或Collector發行版,為不支持OpenTelemetry協議(OTLP)的自定義后端創建新的輸出器,以及為非標準上下文傳播格式創建自定義傳播器等。雖然大多數用戶可能不需要擴展OpenTelemetry,但該項目在幾乎每一層都提供了擴展的可能性。
OpenTelemetry是云原生計算基金會(CNCF)的一個項目,由OpenTracing和OpenCensus兩個先前的項目合并而成。這兩個項目都旨在解決代碼檢測和將遙測數據發送到可觀測性后端方面缺乏標準的問題。由于任何一個項目都無法獨立完全解決這個問題,它們合并成立了OpenTelemetry,結合了各自的優勢,提供了一個統一的解決方案。
2 可觀測性概念
可觀測性允許你通過向系統提出問題來從外部了解系統,而無需知道其內部工作原理。此外,它還可以讓你輕松地對新問題(即"未知的未知")進行故障排除和處理。它還可以幫助你回答"為什么會發生這種情況?"這個問題。
要對系統提出這些問題,應用程序必須經過適當的檢測。也就是說,應用程序代碼必須發出諸如跟蹤、指標和日志等信號。當開發人員不需要添加更多檢測來排查問題時,就說明應用程序已經得到了適當的檢測,因為他們已經擁有了所需的所有信息。
OpenTelemetry是一種機制,通過它可以對應用程序代碼進行檢測,以幫助使系統可觀測。
遙測(Telemetry)是指系統及其行為發出的數據。數據可以以跟蹤、指標和日志的形式出現。
可靠性回答了這個問題:“服務是否在做用戶期望它做的事情?”,一個系統可能100%的時間都在運行,但是如果當用戶點擊"添加到購物車"將一雙黑色鞋子添加到購物車時,系統并不總是添加黑色鞋子,那么該系統可能就是不可靠的。
指標是一段時間內關于基礎設施或應用程序的數值數據的聚合。例如:系統錯誤率、CPU利用率和給定服務的請求率。
SLI(服務級別指標,Service Level Indicator)代表對服務行為的一種衡量。好的SLI從用戶的角度衡量你的服務。SLI的一個示例可以是網頁加載的速度。
SLO(服務級別目標,Service Level Objective)代表了向組織/其他團隊傳達可靠性的方式。這是通過將一個或多個SLI與業務價值相關聯來實現的。
分布式跟蹤可以讓你觀察請求在復雜的分布式系統中如何傳播。分布式跟蹤提高了應用程序或系統健康狀況的可見性,并允許你調試難以在本地重現的行為。對于通常具有不確定性問題或太復雜而無法在本地重現的分布式系統來說,它是必不可少的。
要理解分布式跟蹤,需要了解其每個組件的作用:日志、Span和跟蹤。
(1)日志是由服務或其他組件發出的帶有時間戳的消息。與跟蹤不同,它們不一定與任何特定的用戶請求或事務相關聯。你幾乎可以在軟件的任何地方找到日志。在過去,開發人員和運維人員都非常依賴日志來幫助他們理解系統行為。
I, [2021-02-23T13:26:23.505892 #22473] INFO -- : [6459ffe1-ea53-4044-aaa3-bf902868f730] Started GET "/" for ::1 at 2021-02-23 13:26:23 -0800
日志不足以用于跟蹤代碼執行,因為它們通常缺乏上下文信息,例如它們從何處被調用。
當日志作為Span的一部分或與跟蹤和Span關聯時,它們會變得更加有用。
(2)Span代表一個工作單元或操作。Span跟蹤請求所執行的特定操作,描繪出在執行該操作期間發生的情況。
Span包含名稱、時間相關數據、結構化日志消息和其他元數據(即屬性),以提供有關其跟蹤的操作的信息。
Span屬性是附加到Span的元數據,以下表格包含Span屬性的示例:
Key | Value |
---|---|
http.request.method | “GET” |
network.protocol.version | “1.1” |
url.path | “/webshop/articles/4” |
url.query | “?s=1” |
server.address | “example.com” |
server.port | 8080 |
url.scheme | “https” |
http.route | “/webshop/articles/:article_id” |
http.response.status_code | 200 |
client.address | “192.0.2.4” |
client.socket.address | “192.0.2.5” (客戶端通過代理) |
user_agent.original | “Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0” |
(3)分布式跟蹤(distributed trace),通常稱為跟蹤,記錄請求(由應用程序或最終用戶發出)在多服務架構(如微服務和無服務器應用程序)中傳播時所采取的路徑。
一個跟蹤由一個或多個Span組成。第一個Span代表根Span。每個根Span代表從開始到結束的請求。父Span下的子Span提供了請求過程中發生的更深入的上下文(或構成請求的步驟)。
如果沒有跟蹤,在分布式系統中查找性能問題的根本原因可能會很有挑戰性。通過將請求在分布式系統中的流動過程分解,跟蹤使調試和理解分布式系統變得不那么令人生畏。
許多可觀測性后端將跟蹤可視化為如下所示的瀑布圖:
瀑布圖顯示了根Span與其子Span之間的父子關系。當一個Span封裝另一個Span時,這也表示嵌套關系。
3 上下文傳播(Context propagation)
通過上下文傳播,信號之間可以相互關聯,無論它們在何處生成。雖然上下文傳播并不局限于追蹤,但它能讓追蹤在跨越進程和網絡邊界、任意分布的服務之間,構建關于系統的因果關系信息。
要理解上下文傳播,需要分別理解兩個概念:上下文和傳播。
(1)上下文(Context):上下文是一個對象,它包含了發送和接收服務(或執行單元)將一個信號與另一個信號相關聯所需的信息。
例如,如果服務 A 調用服務 B,那么服務 A 中 ID 在上下文中的一個跨度(span)將作為服務 B 中創建的下一個跨度的父跨度。上下文中的追蹤 ID(trace ID)也將用于服務 B 中創建的下一個跨度,這意味著該跨度與服務 A 中的跨度屬于同一條追蹤鏈。
(2)傳播(propagation):傳播是在服務和進程之間傳遞上下文的機制。它對上下文對象進行序列化或反序列化,并提供從一個服務傳播到另一個服務的相關信息。
傳播通常由檢測庫(instrumentation libraries)處理,對用戶來說是透明的。如果你需要手動傳播上下文,可以使用傳播器 API(Propagators API)。
OpenTelemetry 維護了幾個官方傳播器。默認的傳播器使用 W3C 追蹤上下文規范(W3C TraceContext specification)指定的頭部信息。
4 追蹤(Traces)
追蹤能讓我們全面了解向應用程序發出請求時發生的情況。無論你的應用程序是一個連接單一數據庫的單體應用,還是一個復雜的服務網格,追蹤對于理解請求在應用程序中所經過的完整 “路徑” 都至關重要。
讓我們通過三個工作單元(用跨度表示)來探究這一點:
- “hello” 跨度
{"name": "hello","context": {"trace_id": "5b8aa5a2d2c872e8321cf37308d69df2","span_id": "051581bf3cb55c13"},"parent_id": null,"start_time": "2022-04-29T18:52:58.114201Z","end_time": "2022-04-29T18:52:58.114687Z","attributes": {"http.route": "some_route1"},"events": [{"name": "Guten Tag!","timestamp": "2022-04-29T18:52:58.114561Z","attributes": {"event_attributes": 1}}]
}
這是根跨度,表示整個操作的開始和結束。注意,它有一個trace_id
字段來指明追蹤,但沒有parent_id
。通過這一點就能判斷它是根跨度。
- “hello - greetings” 跨度
{"name": "hello-greetings","context": {"trace_id": "5b8aa5a2d2c872e8321cf37308d69df2","span_id": "5fb397be34d26b51"},"parent_id": "051581bf3cb55c13","start_time": "2022-04-29T18:52:58.114304Z","end_time": "2022-04-29T22:52:58.114561Z","attributes": {"http.route": "some_route2"},"events": [{"name": "hey there!","timestamp": "2022-04-29T18:52:58.114561Z","attributes": {"event_attributes": 1}},{"name": "bye now!","timestamp": "2022-04-29T18:52:58.114585Z","attributes": {"event_attributes": 1}}]
}
這個跨度封裝了特定任務,比如打招呼,它的父跨度是 “hello” 跨度。注意,它與根跨度共享相同的trace_id
,表明它是同一條追蹤的一部分。此外,它的parent_id
與 “hello” 跨度的span_id
相匹配。
- “hello - salutations” 跨度
{"name": "hello-salutations","context": {"trace_id": "5b8aa5a2d2c872e8321cf37308d69df2","span_id": "93564f51e1abe1c2"},"parent_id": "051581bf3cb55c13","start_time": "2022-04-29T18:52:58.114492Z","end_time": "2022-04-29T18:52:58.114631Z","attributes": {"http.route": "some_route3"},"events": [{"name": "hey there!","timestamp": "2022-04-29T18:52:58.114561Z","attributes": {"event_attributes": 1}}]
}
這個跨度表示此次追蹤中的第三個操作,和前一個跨度一樣,它是 “hello” 跨度的子跨度。這也使得它和 “hello - greetings” 跨度互為兄弟跨度。
這三個 JSON 塊都共享相同的trace_id
,parent_id
字段代表了層級關系。這就構成了一條追蹤!
每個跨度看起來都像結構化日志。某種程度上確實如此!一種理解追蹤的方式是,追蹤是一系列包含上下文、關聯關系、層級結構等信息的結構化日志集合。不過,這些 “結構化日志” 可能來自不同的進程、服務、虛擬機、數據中心等等。這就是追蹤能夠呈現任何系統端到端視圖的原因。
為了理解 OpenTelemetry 中的追蹤機制,讓我們來看一下在為代碼添加檢測時涉及的組件列表。
追蹤器提供者(Tracer Provider):追蹤器提供者(有時稱為TracerProvider
)是Tracer
的工廠。在大多數應用程序中,追蹤器提供者只會初始化一次,其生命周期與應用程序的生命周期一致。追蹤器提供者的初始化還包括資源(Resource)和導出器(Exporter)的初始化。這通常是使用 OpenTelemetry 進行追蹤的第一步。在一些語言的 SDK 中,已經為你初始化好了全局追蹤器提供者。
追蹤器(Tracer):追蹤器創建跨度,這些跨度包含有關特定操作(比如服務中的一個請求)的更多信息。追蹤器由追蹤器提供者創建。
追蹤導出器(Trace Exporters):追蹤導出器將追蹤數據發送給使用者。這個使用者可以是用于調試和開發階段的標準輸出、OpenTelemetry 收集器,或者你選擇的任何開源或廠商后端。
上下文傳播(Context Propagation):上下文傳播是實現分布式追蹤的核心概念。通過上下文傳播,跨度之間可以相互關聯,并組裝成一條追蹤,無論跨度在何處生成。有關此主題的更多信息,請參閱上下文傳播概念頁面。
跨度(Spans):跨度代表一個工作單元或操作,是追蹤的基本構建塊。在 OpenTelemetry 中,跨度包含以下信息:
- 名稱
- 父跨度 ID(根跨度為空)
- 開始和結束時間戳
- 跨度上下文
- 屬性
- 跨度事件
- 跨度鏈接
- 跨度狀態
{"name": "/v1/sys/health","context": {"trace_id": "7bba9f33312b3dbb8b2c2c62bb7abe2d","span_id": "086e83747d0e381e"},"parent_id": "","start_time": "2021-10-22 16:04:01.209458162 +0000 UTC","end_time": "2021-10-22 16:04:01.209514132 +0000 UTC","status_code": "STATUS_CODE_OK","status_message": "","attributes": {"net.transport": "IP.TCP","net.peer.ip": "172.17.0.1","net.peer.port": "51820","net.host.ip": "10.177.2.152","net.host.port": "26040","http.method": "GET","http.target": "/v1/sys/health","http.server_name": "mortar-gateway","http.route": "/v1/sys/health","http.user_agent": "Consul Health Check","http.scheme": "http","http.host": "10.177.2.152:26040","http.flavor": "1.1"},"events": [{"name": "","message": "OK","timestamp": "2021-10-22 16:04:01.209512872 +0000 UTC"}]
}
跨度可以嵌套,這由父跨度 ID 的存在暗示:子跨度代表子操作。這使得跨度能夠更準確地記錄應用程序中完成的工作。
跨度上下文(Span Context):跨度上下文是每個跨度上的不可變對象,包含以下內容:
- 代表該跨度所屬追蹤的追蹤 ID。
- 該跨度的跨度 ID。
- 追蹤標志(Trace Flags),一種二進制編碼,包含有關追蹤的信息。
- 追蹤狀態(Trace State),一個鍵值對列表,可以攜帶特定于供應商的追蹤信息。
跨度上下文是跨度的一部分,會與分布式上下文(Distributed Context)和行李(Baggage)一起進行序列化和傳播。
由于跨度上下文包含追蹤 ID,因此在創建跨度鏈接時會用到它。
屬性(Attributes):屬性是鍵值對,包含元數據,可用于注釋跨度,以攜帶有關其正在跟蹤的操作的信息。
例如,如果一個跨度跟蹤電子商務系統中向用戶購物車添加商品的操作,可以捕獲用戶 ID、要添加到購物車的商品 ID 以及購物車 ID。
可以在跨度創建期間或之后添加屬性。建議在跨度創建時添加屬性,以便 SDK 采樣能夠獲取這些屬性。如果必須在跨度創建后添加值,可以使用該值更新跨度。
屬性具有以下規則,每種語言的 SDK 都會實現:
- 鍵必須是非空字符串值。
- 值必須是非空字符串、布爾值、浮點值、整數值,或者是這些值的數組。
此外,還有語義屬性(Semantic Attributes),這是常見操作中通常存在的元數據的命名約定。盡可能使用語義屬性命名有助于在不同系統中對常見類型的元數據進行標準化。
跨度事件(Span Events):跨度事件可以看作是跨度上的結構化日志消息(或注釋),通常用于表示跨度持續時間內某個有意義的時間點。
例如,考慮網頁瀏覽器中的兩種場景:
- 跟蹤頁面加載。
- 表示頁面何時變為可交互狀態。
第一種場景最好使用跨度,因為它是一個有開始和結束的操作。
第二種場景最好使用跨度事件,因為它代表一個有意義的時間點。
何時使用跨度事件與跨度屬性:由于跨度事件也包含屬性,因此何時使用事件而不是屬性并不總是一目了然。在做決定時,可以考慮特定時間戳是否有意義。
例如,當使用跨度跟蹤一個操作并且該操作完成時,可能希望將操作數據添加到遙測數據中。
如果操作完成的時間戳有意義或相關,則將數據附加到跨度事件中。
如果時間戳沒有意義,則將數據作為跨度屬性附加。
跨度鏈接(Span Links):跨度鏈接用于將一個跨度與一個或多個跨度關聯起來,暗示它們之間的因果關系。例如,假設有一個分布式系統,其中一些操作由一條追蹤進行跟蹤。
作為對其中某些操作的響應,會將一個額外操作排入隊列等待執行,但它的執行是異步的。我們也可以用一條追蹤來跟蹤后續操作。
我們希望將后續操作的追蹤與第一個追蹤關聯起來,但我們無法預測后續操作何時開始。我們需要關聯這兩條追蹤,這時就可以使用跨度鏈接。
你可以將第一條追蹤的最后一個跨度鏈接到第二條追蹤的第一個跨度。這樣,它們就具有了因果關聯。
跨度鏈接是可選的,但它是將追蹤跨度相互關聯的有效方式。
跨度狀態(Span Status):每個跨度都有一個狀態。可能的值有三種:
Unset
Error
Ok
默認值是Unset
。狀態為Unset
的跨度表示它所跟蹤的操作已成功完成且沒有錯誤。
當跨度狀態為Error
時,意味著它所跟蹤的操作發生了錯誤。例如,這可能是由于服務器處理請求時出現 HTTP 500 錯誤。
當跨度狀態為Ok
時,意味著應用程序開發人員明確將該跨度標記為無錯誤。雖然這可能不太直觀,但當已知跨度已無錯誤完成時,并不需要將跨度狀態設置為Ok
,因為Unset
已經涵蓋了這種情況。Ok
的作用是表示用戶明確設置的、關于跨度狀態的 “最終確定”。在開發人員希望對跨度狀態只有 “成功” 這一種解釋的任何情況下,Ok
都很有用。
再次強調:Unset
表示跨度無錯誤完成。Ok
表示開發人員明確將跨度標記為成功。在大多數情況下,無需明確將跨度標記為Ok
。
跨度類型(Span Kind):創建跨度時,它的類型可以是Client
、Server
、Internal
、Producer
、Consumer
中的一種。跨度類型為追蹤后端提供了關于如何組裝追蹤的提示。根據 OpenTelemetry 規范,服務器跨度的父跨度通常是遠程客戶端跨度,客戶端跨度的子跨度通常是服務器跨度。類似地,消費者跨度的父跨度始終是生產者跨度,生產者跨度的子跨度始終是消費者跨度。如果未指定跨度類型,則默認認為是內部跨度。
- 客戶端(Client):客戶端跨度表示同步的傳出遠程調用,例如傳出的 HTTP 請求或數據庫調用。請注意,在此上下文中,“同步” 并非指
async/await
,而是指該調用不會被排入隊列等待后續處理。 - 服務器(Server):服務器跨度表示同步的傳入遠程調用,例如傳入的 HTTP 請求或遠程過程調用。
- 內部(Internal):內部跨度表示不跨越進程邊界的操作。比如對函數調用或 Express 中間件進行檢測時,可能會使用內部跨度。
- 生產者(Producer):生產者跨度表示創建一個可能在以后異步處理的任務。它可能是一個遠程任務,比如插入作業隊列中的任務,也可能是由事件監聽器處理的本地任務。
- 消費者(Consumer):消費者跨度表示處理由生產者創建的任務,可能在生產者跨度結束很長時間后才開始。
5. 指標(Metrics)
指標是在運行時對服務進行的測量。捕獲測量值的時刻稱為指標事件,它不僅包含測量值本身,還包括測量的時間以及相關元數據。
應用程序和請求指標是可用性和性能的重要指標。自定義指標可以深入了解可用性指標如何影響用戶體驗或業務。收集到的數據可用于在出現故障時發出警報,或在需求高峰期觸發調度決策,自動擴展部署規模。
為了了解 OpenTelemetry 中指標的工作原理,讓我們來看一下在為代碼添加檢測時涉及的組件列表。
度量器提供者(Meter Provider):度量器提供者(有時稱為 MeterProvider)是度量器(Meter)的工廠。在大多數應用程序中,度量器提供者只會初始化一次,其生命周期與應用程序的生命周期一致。度量器提供者的初始化還包括資源(Resource)和導出器(Exporter)的初始化。這通常是使用 OpenTelemetry 進行度量的第一步。在一些語言的 SDK 中,已經為你初始化好了全局度量器提供者。
度量器(Meter):度量器創建指標工具,用于在運行時捕獲有關服務的測量值。度量器由度量器提供者創建。
指標導出器(Metric Exporter):指標導出器將指標數據發送給使用者。這個使用者可以是開發期間用于調試的標準輸出、OpenTelemetry 收集器,或者你選擇的任何開源或廠商后端。
指標工具(Metric Instruments):在 OpenTelemetry 中,測量值由指標工具捕獲。一個指標工具由以下部分定義:
- 名稱
- 類型
- 單位(可選)
- 描述(可選)
名稱、單位和描述由開發人員選擇,或者對于請求和進程指標等常見指標,通過語義約定來定義。
指標工具類型有以下幾種:
- 計數器(Counter):一種隨時間累積的值 —— 你可以把它想象成汽車的里程表,它只會增加。
- 異步計數器(Asynchronous Counter):與計數器類似,但每次導出時收集一次。如果你無法獲取連續的增量,只能獲取聚合值,就可以使用它。
- 上下計數器(UpDownCounter):一種隨時間累積的值,但也可以減少。例如隊列長度,它會隨著隊列中工作項數量的變化而增加或減少。
- 異步上下計數器(Asynchronous UpDownCounter):與上下計數器類似,但每次導出時收集一次。如果你無法獲取連續的變化,只能獲取聚合值(例如當前隊列大小),可以使用它。
- 儀表盤(Gauge):在讀取時測量當前值。例如汽車的燃油表。儀表盤是同步的。
- 異步儀表盤(Asynchronous Gauge):與儀表盤類似,但每次導出時收集一次。如果你無法獲取連續的變化,只能獲取聚合值,可以使用它。
- 直方圖(Histogram):一種客戶端對值的聚合,例如請求延遲。如果你對值的統計信息感興趣,直方圖是個不錯的選擇。例如:有多少請求的處理時間少于 1 秒?
聚合(Aggregation):除了指標工具,聚合的概念也很重要,需要理解。聚合是一種技術,它將大量測量值合并為關于在某個時間窗口內發生的指標事件的精確或估計統計信息。OTLP 協議傳輸此類聚合后的指標。OpenTelemetry API 為每個指標工具提供了默認聚合,可使用視圖(Views)進行覆蓋。OpenTelemetry 項目旨在提供可視化工具和遙測后端支持的默認聚合。
與請求追蹤不同,請求追蹤旨在捕獲請求的生命周期并為請求的各個部分提供上下文,而指標旨在提供匯總的統計信息。指標的一些用例示例包括:
- 按協議類型報告服務讀取的總字節數。
- 報告讀取的總字節數和每個請求的字節數。
- 報告系統調用的持續時間。
- 報告請求大小以確定趨勢。
- 報告進程的 CPU 或內存使用情況。
- 報告賬戶的平均余額值。
- 報告當前正在處理的活動請求。
視圖(Views):視圖為 SDK 用戶提供了自定義 SDK 輸出指標的靈活性。你可以自定義要處理或忽略哪些指標工具。你還可以自定義聚合方式以及希望在指標中報告哪些屬性。
6. 日志(Logs)
日志是帶有時間戳的文本記錄,可以是結構化的(推薦),也可以是非結構化的,并帶有可選的元數據。在所有的遙測信號中,日志的歷史最為悠久。大多數編程語言都有內置的日志記錄功能,或者有知名且廣泛使用的日志記錄庫。
OpenTelemetry 并未定義專門的 API 或 SDK 來創建日志。相反,OpenTelemetry 日志是已經從某個日志記錄框架或基礎設施組件中獲得的現有日志。OpenTelemetry 的軟件開發工具包(SDK)和自動檢測功能利用多個組件,將日志與追蹤自動關聯起來。
OpenTelemetry 對日志的支持旨在與現有的日志完全兼容,它提供了為這些日志添加額外上下文的功能,以及一個通用工具包,用于將來自許多不同來源的日志解析并處理成一種通用格式。
OpenTelemetry 收集器提供了多個處理日志的工具:
- 多個接收器,用于解析來自特定、已知日志數據源的日志。
- 文件日志接收器(filelogreceiver),它可以從任何文件中讀取日志,并提供從不同格式解析日志的功能,或者使用正則表達式進行解析。
- 像轉換處理器(transformprocessor)這樣的處理器,它允許解析嵌套數據、展平嵌套結構、添加 / 刪除 / 更新值等等。
- 導出器,它允許以非 OpenTelemetry 格式發出日志數據。
采用 OpenTelemetry 的第一步通常是部署一個收集器,將其作為通用的日志記錄代理。
在應用程序中,OpenTelemetry 日志是使用任何日志記錄庫或內置的日志記錄功能創建的。當添加自動檢測功能或激活 SDK 時,OpenTelemetry會自動將現有的日志與任何活動的追蹤和跨度關聯起來,并將日志主體附上它們的ID。換句話說,OpenTelemetry 會自動關聯日志和追蹤。
從技術上講,OpenTelemetry 并不區分結構化日志和非結構化日志。可以將現有的任何日志用于 OpenTelemetry。然而,并非所有的日志格式都同樣有用!特別推薦在生產環境的可觀測性中使用結構化日志,因為它們易于大規模解析和分析。以下部分解釋了結構化、非結構化和半結構化日志之間的區別。
結構化日志是一種文本格式遵循一致的、機器可讀格式的日志。對于應用程序來說,最常見的格式之一是 JSON:
{"timestamp": "2024-08-04T12:34:56.789Z","level": "INFO","service": "user-authentication","environment": "production","message": "User login successful","context": {"userId": "12345","username": "johndoe","ipAddress": "192.168.1.1","userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"},"transactionId": "abcd-efgh-ijkl-mnop","duration": 200,"request": {"method": "POST","url": "/api/v1/login","headers": {"Content-Type": "application/json","Accept": "application/json"},"body": {"username": "johndoe","password": "******"}},"response": {"statusCode": 200,"body": {"success": true,"token": "jwt-token-here"}}
}
對于基礎設施組件,通常使用通用日志格式(CLF):
127.0.0.1 - johndoe [04/Aug/2024:12:34:56 -0400] "POST /api/v1/login HTTP/1.1" 200 1234
將不同的結構化日志格式混合在一起的情況也很常見。例如,擴展日志格式(ELF)日志可以將 JSON 與 CLF 日志中以空格分隔的數據混合在一起。
192.168.1.1 - johndoe [04/Aug/2024:12:34:56 -0400] "POST /api/v1/login HTTP/1.1" 200 1234 "http://example.com" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36" {"transactionId": "abcd-efgh-ijkl-mnop", "responseTime": 150, "requestBody": {"username": "johndoe"}, "responseHeaders": {"Content-Type": "application/json"}}
為了充分利用這種日志,需要將 JSON 部分和與 ELF 相關的部分都解析成一種共享格式,以便在可觀測性后端更輕松地進行分析。OpenTelemetry 收集器中的文件日志接收器包含了解析此類日志的標準化方法。
結構化日志是使用日志的首選方式。由于結構化日志以一致的格式發出,它們易于解析,這使得它們在 OpenTelemetry 收集器中更容易進行預處理,與其他數據關聯,并最終在可觀測性后端進行分析。
非結構化日志是不遵循一致結構的日志。它們可能更便于人類閱讀,并且通常在開發過程中使用。然而,在生產環境的可觀測性中不建議使用非結構化日志,因為大規模解析和分析它們要困難得多。
[ERROR] 2024-08-04 12:45:23 - Failed to connect to database. Exception: java.sql.SQLException: Timeout expired. Attempted reconnect 3 times. Server: db.example.com, Port: 5432
System reboot initiated at 2024-08-04 03:00:00 by user: admin. Reason: Scheduled maintenance. Services stopped: web-server, database, cache. Estimated downtime: 15 minutes.
DEBUG - 2024-08-04 09:30:15 - User johndoe performed action: file_upload. Filename: report_Q3_2024.pdf, Size: 2.3 MB, Duration: 5.2 seconds. Result: Success
在生產環境中存儲和分析非結構化日志是可行的,不過你可能需要做大量工作來解析它們或以其他方式進行預處理,使其能夠被機器讀取。例如,上述三條日志需要使用正則表達式來解析它們的時間戳,并需要自定義解析器來一致地提取日志消息的主體。對于日志記錄后端來說,通常需要這樣做才能知道如何按時間戳對日志進行排序和組織。雖然為了分析目的解析非結構化日志是可能的,但這可能比切換到結構化日志記錄(例如通過應用程序中的標準日志記錄框架)需要做更多的工作。
半結構化日志是一種使用一些自洽模式來區分數據,從而使其能夠被機器讀取的日志,但在不同系統之間,數據之間可能不會使用相同的格式和分隔符。
2024-08-04T12:45:23Z level=ERROR service=user-authentication userId=12345 action=login message="Failed login attempt" error="Invalid password" ipAddress=192.168.1.1 userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
雖然半結構化日志能夠被機器讀取,但可能需要幾種不同的解析器才能進行大規模分析。
以下概念和組件列表為 OpenTelemetry 的日志記錄支持提供了動力。
Log Appender / Bridge 日志附加器/橋接器:作為應用程序開發人員,不應該直接調用日志橋接器 API,因為它是提供給日志記錄庫的作者用于構建日志附加器 / 橋接器的。相反,只需使用喜歡的日志記錄庫,并將其配置為使用一個能夠將日志發送到 OpenTelemetry 日志記錄導出器(LogRecordExporter)的日志附加器(或日志橋接器)。
Logger Provider 記錄器提供程序:(有時稱為 LoggerProvider)是記錄器(Logger)的工廠。在大多數情況下,記錄器提供者只會初始化一次,其生命周期與應用程序的生命周期一致。記錄器提供者的初始化還包括資源和導出器的初始化。這是日志橋接器 API 的一部分,并且只有當你是日志記錄庫的作者時才應該使用。
Logger 記錄器:記錄器創建日志記錄。記錄器由記錄器提供者創建。這是日志橋接器 API 的一部分,并且只有當你是日志記錄庫的作者時才應該使用。
Log Record Exporter 日志記錄導出器:日志記錄導出器將日志記錄發送給使用者。這個使用者可以是用于調試和開發階段的標準輸出、OpenTelemetry 收集器,或者你選擇的任何開源或廠商后端。
Log Record 日志記錄:日志記錄表示一個事件的記錄。在 OpenTelemetry 中,一個日志記錄包含兩種類型的字段:
- 具有特定類型和含義的命名頂級字段
- 任意值和類型的資源和屬性字段
頂級字段如下:
字段名稱 | 描述 |
---|---|
時間戳(Timestamp) | 事件發生的時間。 |
觀測時間戳(ObservedTimestamp) | 觀測到事件的時間。 |
追蹤 ID(TraceId) | 請求的追蹤 ID。 |
跨度 ID(SpanId) | 請求的跨度 ID。 |
追蹤標志(TraceFlags) | W3C 追蹤標志。 |
嚴重程度文本(SeverityText) | 嚴重程度文本(也稱為日志級別)。 |
嚴重程度數值(SeverityNumber) | 嚴重程度的數值。 |
主體(Body) | 日志記錄的主體內容。 |
資源(Resource) | 描述日志的來源。 |
檢測范圍(InstrumentationScope) | 描述發出日志的范圍。 |
屬性(Attributes) | 關于事件的其他信息。 |
7. 關聯數據(Baggage)
在 OpenTelemetry 中,關聯數據是與上下文并存的上下文信息。關聯數據是一個鍵值對存儲,這意味著它允許你在傳遞上下文的同時,傳播任何你希望傳遞的數據。關聯數據使得你能夠在不同服務和進程間傳遞數據,讓這些數據在其他服務中添加到追蹤、指標或日志中時可用。
例如,假設在請求開始時你有一個clientId
,你希望這個 ID 在一條追蹤中的所有跨度、另一個服務中的某些指標以及請求過程中的某些日志中都可用。由于追蹤可能跨多個服務,你需要某種方法來傳播這些數據,而無需在代碼庫中的多個地方復制clientId
。
通過使用上下文傳播在這些服務間傳遞關聯數據,clientId
就可以添加到任何額外的跨度、指標或日志中。此外,檢測工具會自動為你傳播關聯數據。
關聯數據最適合用于將通常僅在請求開始時可用的信息傳遞到下游。例如,這可以包括賬戶標識、用戶 ID、產品 ID 和源 IP 地址等。
使用關聯數據傳播這些信息,有助于在后端對遙測數據進行更深入的分析。例如,如果你在追蹤數據庫調用的跨度中包含用戶 ID 等信息,就可以更輕松地回答諸如 “哪些用戶遇到了最慢的數據庫調用?” 這類問題。你還可以記錄有關下游操作的信息,并在日志數據中包含相同的用戶 ID。
敏感的關聯數據項可能會被共享給非預期的資源,比如第三方 API。這是因為自動檢測會在大多數服務的網絡請求中包含關聯數據。具體來說,關聯數據和追蹤上下文的其他部分會在 HTTP 頭中發送,這使得任何檢查網絡流量的人都能看到這些數據。如果網絡流量在你的網絡內部受到限制,這種風險可能不適用,但要記住,下游服務可能會將關聯數據傳播到你的網絡之外。
此外,沒有內置的完整性檢查機制來確保關聯數據項屬于你,因此在讀取關聯數據時要格外小心。
關于關聯數據,需要注意的重要一點是,它是一個獨立的鍵值對存儲,在未明確添加的情況下,與跨度、指標或日志的屬性沒有關聯。
要將關聯數據項添加為屬性,你需要顯式地從關聯數據中讀取數據,并將其作為屬性添加到跨度、指標或日志中。
由于關聯數據的一個常見用例是在整個追蹤過程中向跨度屬性添加數據,因此幾種編程語言都有 “關聯數據跨度處理器”,可以在創建跨度時將關聯數據中的數據作為屬性添加。
8. 檢測方式
一個系統要具備可觀測性,就必須進行檢測:也就是說,系統組件的代碼必須發出諸如追蹤、指標和日志等信號。
使用 OpenTelemetry,主要可以通過兩種方式對代碼進行檢測:
- 基于代碼的解決方案:借助針對大多數語言的官方 API 和 SDK 來實現。
- 無代碼解決方案。
基于代碼的解決方案能讓你從應用程序本身獲取更深入的洞察和豐富的遙測數據。它們允許你使用 OpenTelemetry API 從應用程序生成遙測數據,這是對無代碼解決方案所生成遙測數據的重要補充。
無代碼解決方案非常適合入門階段,或者當你無法修改需要獲取遙測數據的應用程序時使用。它們能從你使用的庫和 / 或應用程序運行的環境中提供豐富的遙測數據。換個角度看,它們提供了關于應用程序邊緣發生情況的信息。
你可以同時使用這兩種解決方案。
OpenTelemetry 提供的不僅僅是無代碼和基于代碼的遙測解決方案。以下這些也是 OpenTelemetry 的一部分:
- 庫可以將 OpenTelemetry API 作為依賴項,這對使用該庫的應用程序不會產生影響,除非導入了 OpenTelemetry SDK。
- 對于每種信號,你都有多種方法來創建、處理和導出它們。
- 由于實現中內置了上下文傳播功能,無論信號在何處生成,你都可以將它們關聯起來。
- 資源(Resources)和檢測范圍(Instrumentation Scopes)允許按照不同實體(如主機、操作系統或 K8s 集群)對信號進行分組。
- 每個特定語言的 API 和 SDK 實現都遵循 OpenTelemetry 規范的要求和預期。
- 語義約定(Semantic Conventions)提供了一種通用的命名模式,可用于在不同代碼庫和平臺之間實現標準化。
- 無代碼:了解如何在無需編寫代碼的情況下為應用程序添加可觀測性。
- 基于代碼:了解設置基于代碼的檢測的基本步驟。
- 庫:了解如何為你的庫添加原生檢測功能。
9. 組件
目前,OpenTelemetry 由幾個主要組件構成:
- 規范(Specification)
- 收集器(Collector)
- 特定語言的 API 和 SDK 實現(Language-specific API & SDK implementations)
- 檢測庫(Instrumentation Libraries)
- 導出器(Exporters)
- 無代碼檢測(Zero-Code Instrumentation)
- 資源探測器(Resource Detectors)
- 跨服務傳播器(Cross Service Propagators)
- 采樣器(Samplers)
- Kubernetes 操作符(Kubernetes operator)
- 函數即服務資源(Function as a Service assets)
OpenTelemetry 使你無需使用特定供應商的 SDK 和工具來生成和導出遙測數據。
Specification 規范:描述了所有實現的跨語言要求和預期。除了術語定義之外,該規范還定義了以下內容:
-
API:定義用于生成和關聯追蹤、指標以及日志數據的數據類型和操作。
-
SDK:定義特定語言實現 API 的要求。配置、數據處理和導出的相關概念也在此定義。
-
數據:定義 OpenTelemetry 協議(OTLP)以及遙測后端可以支持的與供應商無關的語義約定。
-
收集器:OpenTelemetry 收集器是一個與供應商無關的代理,它可以接收、處理和導出遙測數據。它支持以多種格式(例如 OTLP、Jaeger、Prometheus 以及許多商業 / 專有工具格式)接收遙測數據,并將數據發送到一個或多個后端。它還支持在數據導出之前對遙測數據進行處理和過濾。
-
特定語言的 API 和 SDK 實現:OpenTelemetry 還有特定語言的 SDK,使你能夠使用 OpenTelemetry API,以你選擇的語言生成遙測數據,并將這些數據導出到首選的后端。這些 SDK 還允許你納入針對常用庫和框架的檢測庫,以便與應用程序中的手動檢測相連接。
-
檢測庫:OpenTelemetry 支持大量組件,這些組件能從受支持語言的流行庫和框架中生成相關的遙測數據。例如,HTTP 庫的入站和出站 HTTP 請求會生成關于這些請求的數據。
OpenTelemetry 的一個理想目標是,所有流行庫默認都具備可觀測性,這樣就無需額外的依賴項。
導出器:將遙測數據發送到 OpenTelemetry 收集器,以確保數據正確導出。在生產環境中使用收集器是一種最佳實踐。若要可視化遙測數據,可以將其導出到諸如 Jaeger、Zipkin、Prometheus 或特定供應商的后端。
在導出器中,OpenTelemetry 協議(OTLP)導出器在設計時考慮了 OpenTelemetry 數據模型,能夠在不丟失任何信息的情況下發出 OTel 數據。此外,許多處理遙測數據的工具都支持 OTLP(如 Prometheus、Jaeger 以及大多數供應商工具),在你有需要時提供了高度的靈活性。
無代碼檢測:在適用的情況下,OpenTelemetry 的特定語言實現提供了一種無需觸及源代碼即可檢測應用程序的方法。雖然底層機制因語言而異,但無代碼檢測會為你的應用程序添加 OpenTelemetry API 和 SDK 功能。此外,它可能會添加一組檢測庫和導出器依賴項。
資源探測器:資源通過資源屬性表示生成遙測數據的實體。例如,在 Kubernetes 容器中運行并生成遙測數據的進程,會有 Pod 名稱、命名空間,可能還有部署名稱。你可以將所有這些屬性包含在資源中。
OpenTelemetry 的特定語言實現可以從OTEL_RESOURCE_ATTRIBUTES
環境變量中檢測資源,并且能夠檢測許多常見實體的資源,如進程運行時、服務、主機或操作系統。
跨服務傳播器:傳播是在服務和進程之間傳輸數據的機制。雖然傳播并不局限于追蹤,但它能讓追蹤在跨越進程和網絡邊界、任意分布的服務之間構建關于系統的因果關系信息。
在絕大多數用例中,上下文傳播是通過檢測庫實現的。如果需要,你也可以自行使用傳播器對諸如跨度上下文和關聯數據(baggage)等橫切關注點進行序列化和反序列化。
采樣器:采樣是一個限制系統生成追蹤數量的過程。OpenTelemetry 的每個特定語言實現都提供了多種頭部采樣器。
Kubernetes 操作符:OpenTelemetry 操作符是 Kubernetes 操作符的一種實現。該操作符管理 OpenTelemetry 收集器,并使用 OpenTelemetry 對工作負載進行自動檢測。
函數即服務資源:OpenTelemetry 支持多種方法來監控不同云供應商提供的函數即服務(Function-as-a-Service)。OpenTelemetry 社區目前提供預構建的 Lambda 層,能夠自動檢測你的應用程序,同時也提供獨立的收集器 Lambda 層選項,可在手動或自動檢測應用程序時使用。
10. 采樣
借助追蹤技術,你可以觀察請求在分布式系統中從一個服務轉移到另一個服務的過程。追蹤對于系統的高級別和深入分析都非常實用。
然而,如果絕大多數請求都成功完成,且延遲在可接受范圍內且無錯誤,那么你并不需要 100% 的追蹤數據來有效觀察應用程序和系統。你只需要進行合理的采樣。
在討論采樣時,使用一致的術語非常重要。一條追蹤或一個跨度被視為 “已采樣” 或 “未采樣”:
- 已采樣:一條追蹤或一個跨度會被處理和導出。因為它被采樣器選中,作為總體的代表,所以被視為 “已采樣”。
- 未采樣:一條追蹤或一個跨度不會被處理或導出。因為它未被采樣器選中,所以被視為 “未采樣”。
有時,這些術語的定義會被混淆。你可能會聽到有人說他們 “對數據進行采樣排除”,或者將未處理或未導出的數據視為 “已采樣”。這些說法都是錯誤的。
采樣是在不損失可見性的前提下降低可觀測性成本的最有效方法之一。雖然還有其他降低成本的方法,如過濾或聚合數據,但這些方法不符合代表性的概念,而代表性在對應用程序或系統行為進行深入分析時至關重要。
代表性是指一個較小的群體能夠準確代表一個較大群體的原則。此外,代表性可以通過數學方法驗證,這意味著你可以高度確信較小的數據樣本能夠準確代表較大的群體。
另外,生成的數據越多,獲得具有代表性的樣本所需的數據就越少。對于高流量系統而言,1% 或更低的采樣率就能非常準確地代表其他 99% 的數據,這種情況很常見。
如果滿足以下任何一個標準,可考慮進行采樣:
- 每秒生成 1000 條或更多的追蹤數據。
- 大部分追蹤數據代表正常流量,數據變化不大。
- 存在一些常見標準,如錯誤或高延遲,這些通常意味著出現了問題。
- 除了錯誤和延遲之外,還有特定領域的標準可用于確定相關數據。
- 可以描述一些常見規則,用于確定數據是應被采樣還是丟棄。
- 有辦法區分不同的服務,以便對高流量和低流量服務進行不同的采樣。
- 能夠將未采樣的數據(以防萬一)路由到低成本存儲系統。
最后,考慮一下整體預算。如果可觀測性預算有限,但有時間進行有效的采樣,那么通常來說采樣是值得的。
采樣可能并不適合你。如果滿足以下任何一個標準,你可能需要避免采樣:
- 生成的數據量極少(每秒幾十條或更少的小追蹤數據)。
- 僅以聚合方式使用可觀測性數據,因此可以預先聚合數據。
- 受到某些情況的限制,如法規禁止丟棄數據(并且無法將未采樣的數據路由到低成本存儲)。
最后,考慮與采樣相關的以下三種成本:
- 有效采樣數據的直接計算成本,如尾部采樣代理的成本。
- 隨著涉及的應用程序、系統和數據增多,維護有效采樣方法的間接工程成本。
- 由于采樣技術不當而遺漏關鍵信息的間接機會成本。
采樣雖然能有效降低可觀測性成本,但如果實施不當,可能會帶來其他意想不到的成本。根據你的可觀測性后端、數據的性質以及有效采樣的嘗試,為可觀測性分配更多資源(無論是使用供應商服務還是自行托管計算資源)可能會更劃算。
頭部采樣是一種盡早做出采樣決策的采樣技術。決定是否采樣或丟棄一個跨度或一條追蹤,不是通過檢查整條追蹤來做出的。
例如,最常見的頭部采樣形式是一致概率采樣,也稱為確定性采樣。在這種情況下,根據追蹤 ID 和期望的采樣追蹤百分比來做出采樣決策。這確保了整條追蹤都被采樣(不會遺漏跨度),并且采樣率一致,比如所有追蹤的 5%。
頭部采樣的優點包括:
- 易于理解
- 易于配置
- 高效
- 可以在追蹤收集管道的任何階段進行
頭部采樣的主要缺點是無法根據整條追蹤中的數據做出采樣決策。例如,僅靠頭部采樣無法確保所有包含錯誤的追蹤都被采樣。在這種情況以及許多其他情況下,你需要使用尾部采樣。
尾部采樣是指在考慮一條追蹤中的所有或大部分跨度之后,再決定是否對該追蹤進行采樣。尾部采樣使你能夠根據追蹤不同部分得出的特定標準來選擇采樣的追蹤,而頭部采樣則沒有這個選項。
可以使用尾部采樣的一些示例包括:
- 始終對包含錯誤的追蹤進行采樣。
- 根據總體延遲對追蹤進行采樣。
- 根據一條追蹤中一個或多個跨度上特定屬性的存在或值對追蹤進行采樣;例如,對來自新部署服務的追蹤進行更多采樣。
- 根據特定標準對追蹤應用不同的采樣率,比如當追蹤僅來自低流量服務時與來自高流量服務時采用不同采樣率。
如你所見,尾部采樣在數據采樣方式上更加精細復雜。對于必須對遙測數據進行采樣的大型系統,幾乎總是需要使用尾部采樣來平衡數據量和數據的有用性。
目前,尾部采樣主要有三個缺點:
- 尾部采樣可能難以實施。根據你可用的采樣技術,它并不總是 “設置好就不用管” 的事情。隨著系統的變化,采樣策略也需要改變。對于大型復雜的分布式系統,實施采樣策略的規則也會很復雜。
- 尾部采樣可能難以操作。實施尾部采樣的組件必須是有狀態的系統,能夠接受和存儲大量數據。根據流量模式,這可能需要幾十個甚至幾百個計算節點,而且這些節點對資源的利用方式各不相同。此外,如果尾部采樣器無法處理接收到的數據量,可能需要 “回退” 到計算量較小的采樣技術。由于這些因素,監控尾部采樣組件以確保它們擁有做出正確采樣決策所需的資源至關重要。
- 如今,尾部采樣器往往是特定供應商的技術。如果你使用付費供應商提供的可觀測性服務,你能使用的最有效的尾部采樣選項可能會受到供應商提供內容的限制。
最后,對于某些系統,尾部采樣可能會與頭部采樣結合使用。例如,一組產生極高流量追蹤數據的服務,可能首先使用頭部采樣,僅對一小部分追蹤進行采樣,然后在遙測管道的后續階段使用尾部采樣,在將數據導出到后端之前做出更精細的采樣決策。這樣做通常是為了防止遙測管道過載。
Once Day
也信美人終作土,不堪幽夢太匆匆......
如果這篇文章為您帶來了幫助或啟發,不妨點個贊👍和關注,再加上一個小小的收藏?!
(。???。)感謝您的閱讀與支持~~~