返回文章列表

日誌紀錄最佳實務探討與實務應用

本文探討日誌紀錄的最佳實務,涵蓋紀錄五大要素:何事、何時、何地、何因、誰,以及錯誤程式碼的管理與應用。同時,文章也關注日誌安全與合規性,探討如何保護敏感資料,並以 Fluentd 為例示範如何過濾和處理日誌,確保系統安全和符合法規要求。

軟體開發 系統設計

在系統開發和維運過程中,有效的日誌紀錄策略至關重要。除了協助問題排查和效能分析,完善的日誌系統也能提升系統安全性,並滿足日益嚴格的法規遵循需求。本文將探討如何實踐有效的日誌紀錄策略,並以 Java 和 Fluentd 為例,示範如何妥善處理日誌資訊,同時兼顧效能和安全性。我們將探討紀錄的五大要素:何事、何時、何地、何因、誰,並說明如何有效地管理和應用錯誤程式碼,提升系統的可維護性。此外,文章也將探討日誌安全和合規性的重要性,特別是如何在日誌中保護敏感資料,例如個人身份資訊或信用卡資料,並示範如何使用 Fluentd 過濾和處理日誌,確保系統安全和符合 GDPR 等法規要求。

紀錄最佳實踐:探討與實務應用

紀錄的背景脈絡:全面掌握「何事、何時、何地、何因、誰」

在進行系統紀錄時,我們需要考慮五個關鍵要素:何事(what)、何時(when)、何地(where)、何因(why)和誰(who)。這些資訊對於問題排查、系統維護和安全監控至關重要。

何事(What):事件的詳細描述

紀錄事件的詳細描述是至關重要的。對於錯誤處理路徑,效能不應是主要考慮因素,因為這部分程式碼應該只在極少數情況下執行。通常情況下,資訊不足遠比資訊過多更糟糕。

try {
    // 可能會丟擲例外狀況的程式碼
} catch (Exception e) {
    logger.error("發生例外狀況:{}", e.getMessage(), e);
    // 紀錄相關的資料值
}

內容解密:

  1. 錯誤處理與紀錄:在 try-catch 區塊中,我們捕捉例外狀況並使用日誌記錄器(logger)將錯誤訊息和堆積疊追蹤(stack trace)記錄下來。
  2. 資料值的紀錄:除了錯誤訊息外,紀錄相關的資料值對於問題排查非常重要,可以幫助開發人員瞭解錯誤發生的上下文。

何時(When):事件發生的時間戳記

事件發生的時間戳記對於問題排查和系統監控至關重要。透過時間戳記,我們可以瞭解事件的先後順序和時間間隔。

logger.info("事件發生時間:{}", Instant.now());

內容解密:

  1. 時間戳記的紀錄:使用 Instant.now() 取得目前的時間戳記,並將其記錄到日誌中。
  2. 時間戳記的重要性:時間戳記可以幫助我們瞭解事件的發生順序和時間間隔,對於問題排查和系統監控非常重要。

何地(Where):事件發生的位置

事件發生的位置資訊可以幫助我們快速定位問題所在。這包括程式碼的位置、伺服器或容器資訊等。

logger.error("錯誤發生在 {}:{}:{}", 
             Thread.currentThread().getStackTrace()[1].getClassName(),
             Thread.currentThread().getStackTrace()[1].getMethodName(),
             Thread.currentThread().getStackTrace()[1].getLineNumber());

內容解密:

  1. 程式碼位置的紀錄:透過 Thread.currentThread().getStackTrace() 取得目前執行緒的堆積疊追蹤資訊,並記錄錯誤發生的類別名稱、方法名稱和行號。
  2. 位置資訊的重要性:這些資訊可以幫助開發人員快速定位問題所在,提高問題排查的效率。

何因(Why):事件發生的原因

瞭解事件發生的原因對於問題排查和系統維護至關重要。我們應該盡可能地記錄事件發生的原因和相關的資料值。

try {
    int result = divide(a, b);
} catch (ArithmeticException e) {
    logger.error("除以零錯誤:a={}, b={}", a, b, e);
}

內容解密:

  1. 錯誤原因的紀錄:在捕捉到 ArithmeticException 時,記錄錯誤的原因和相關的資料值(a 和 b)。
  2. 資料值的重要性:記錄相關的資料值可以幫助開發人員瞭解錯誤發生的上下文和原因。

誰(Who):事件相關的身份資訊

紀錄事件相關的身份資訊可能會涉及隱私和法規遵從性問題。我們需要謹慎處理「誰」的資訊,並考慮使用代理資料來保護個人隱私。

logger.info("使用者 {} 進行了操作", userId);
// 或者使用 session ID
logger.info("Session {} 進行了操作", sessionId);

內容解密:

  1. 身份資訊的處理:在紀錄身份資訊時,需要考慮隱私和法規遵從性問題。
  2. 代理資料的使用:可以使用 session ID 或其他代理資料來代替真實身份資訊,以保護個人隱私。

錯誤程式碼的管理與應用

錯誤程式碼是協助管理和查詢錯誤的重要工具。透過錯誤程式碼,我們可以快速查詢相關的錯誤描述和解決方案。

為什麼需要錯誤程式碼?

  1. 便於查詢和管理:錯誤程式碼提供了一種簡單的方式來查詢和管理錯誤。
  2. 提高效率:透過錯誤程式碼,開發人員和維運人員可以快速找到相關的錯誤描述和解決方案。

如何實作錯誤程式碼?

  1. 建立錯誤程式碼表:建立一個錯誤程式碼表,將錯誤程式碼與錯誤描述和解決方案對應起來。
  2. 在程式碼中使用錯誤程式碼:在程式碼中,使用錯誤程式碼來記錄和查詢錯誤。
// 定義錯誤程式碼
public enum ErrorCode {
    DIVIDE_BY_ZERO("0001", "除以零錯誤"),
    NETWORK_ERROR("0002", "網路連線錯誤");
    
    private String code;
    private String description;
    
    ErrorCode(String code, String description) {
        this.code = code;
        this.description = description;
    }
    
    public String getCode() {
        return code;
    }
    
    public String getDescription() {
        return description;
    }
}

// 使用錯誤程式碼
try {
    int result = divide(a, b);
} catch (ArithmeticException e) {
    logger.error("{}: {}", ErrorCode.DIVIDE_BY_ZERO.getCode(), ErrorCode.DIVIDE_BY_ZERO.getDescription());
}

內容解密:

  1. 定義錯誤程式碼列舉:建立一個 ErrorCode 列舉,將錯誤程式碼與錯誤描述對應起來。
  2. 在程式碼中使用錯誤程式碼:在捕捉到例外狀況時,使用 ErrorCode 列舉來記錄錯誤程式碼和描述。

日誌記錄最佳實踐

在軟體開發過程中,錯誤程式碼的使用能夠輕易地標準化和國際化有關錯誤的檔案。錯誤程式碼不受語言和地區的限制,一旦獲得錯誤程式碼,就可以根據適當的語言查詢相關檔案。

錯誤程式碼編號原則

在建立錯誤程式碼編號時,有幾項需要注意的原則:

  1. 避免使用前導零,除非以字母字元作為字首。這樣可以避免數字被截斷或在格式化時產生額外的工作。
  2. 不要從1開始,最好從該範圍的最低數字開始(例如1000)。
  3. 使錯誤程式碼易於搜尋,例如使用1000作為錯誤程式碼,這樣比AppErr1000更容易被搜尋到。許多資料函式庫和系統的錯誤程式碼都有特定的字首,例如Oracle的DB錯誤程式碼以ORA開頭,WebLogic的錯誤程式碼以BEA開頭。
  4. 不要將所有錯誤程式碼集中在某一處程式碼中,例如某個類別或介面。這樣會導致程式碼被過度依賴,並在系統開發過程中不斷變更。更好的做法是將錯誤程式碼的詳細資訊存放在一個分享的知識函式庫中,例如wiki或協作知識函式庫。
  5. 將錯誤程式碼分組,就像HTTP RFC中所做的那樣,但要保持實用性。一個錯誤程式碼可能在邏輯上屬於多個組別。
  6. 使用字首或字尾來提供產品或子系統層級的範圍,例如BEA-00000和ORA-00000代表了不同Oracle產品的錯誤程式碼。

使用標準錯誤程式碼

一些技術提供了用於表示成功和錯誤的程式碼,例如HTTP(RFC https://tools.ietf.org/html/rfc7231#section-6)。使用這些標準化的錯誤程式碼可以提供更多的上下文和共同的理解,只要它們被正確使用。

程式碼範例:HTTP 狀態碼

HTTP/1.1 413 Payload Too Large
Content-Type: text/html
Content-Length: 1234

<html>
  <body>
    <h1>413 Payload Too Large</h1>
    <p>請求的 payload 過大,請減少資料量後重試。</p>
  </body>
</html>

內容解密:

  1. HTTP/1.1 413 Payload Too Large:表示客戶端傳送的請求實體過大,伺服器拒絕處理該請求。
  2. Content-Type: text/html:指定回應內容的格式為 HTML。
  3. Content-Length: 1234:指定回應內容的長度為 1234 位元組。
  4. HTML 內容提供了人類可讀的錯誤訊息,用於向使用者解釋錯誤原因。

日誌記錄的適當性

日誌記錄的內容可能很難決定。很容易將整個資料結構或物件傾印到日誌事件中,但這種做法會帶來兩個挑戰:

  1. 日誌資料量可能會變得非常龐大,從而過度增加計算和儲存的工作負載。
  2. 可能會記錄敏感資訊

為了避免這些問題,可以透過調整日誌框架的組態來控制記錄的資訊量。與其修改程式碼來取得足夠的資訊,不如透過組態來控制日誌記錄,這樣可以更快地實作變更。

最佳實踐

  1. 區分不同來源的相同錯誤程式碼,在支援資訊中區分出處。
  2. 考慮錯誤程式碼的使用者,一個錯誤可能來自單一位置,但有不同的原因,因此為不同的原因使用不同的錯誤程式碼,這將使支援團隊的工作或修復更加容易。

總之,日誌記錄的最佳實踐需要仔細考慮錯誤程式碼的使用、標準化錯誤程式碼的應用,以及如何適當地記錄日誌資訊,以確保系統的可維護性和安全性。

日誌最佳實踐中的敏感資料保護

軟體變更治理控制可能會要求在發布過程中更加嚴謹,這將減慢日誌檔案支援任務的進行。問題在於,記錄整個資料結構可能會導致日誌中包含敏感資料,例如個人資料或信用卡資料,這些資料都有嚴格的保護規則。重要的是在日誌中提供足夠的上下文而不記錄任何敏感資料。如果事件在早期被記錄到安全的儲存裝置(如資料函式庫),我們可以為該事件分配一個ID。然後,其餘的日誌可以透過記錄已記錄的事件ID來實作。如果需要,這允許我們傳回到資料上下文,而不必將值分散在各個日誌中。

這個問題的一部分超出了我們如何寫應用程式日誌的範圍,還涉及到我們解決方案的設計。最好的說明是HTTP呼叫的處理。在HTTP呼叫到達我們的應用程式伺服器之前,HTTP流量將透過防火牆、負載平衡器、代理伺服器、網路路由器和其他基礎設施元素。當我們實作一個網頁應用程式時,即使您擁有最好的HTTPS組態,頭部資訊也必須是可讀取的,以便將流量路由到其目的地。通常,這些元件會記錄URIs和通常所有的HTTP頭部。頭部可能包含有關處理請求和回應的詳細資訊(例如,頭部包含指示基礎設施是否可以快取內容的屬性)。最終結果是,如果您將敏感值放入payload URIs或頭部,敏感資訊可能會意外地最終被記錄下來。

什麼是敏感資料?

決定什麼資料是敏感的可能很棘手,因為它可以由多種因素驅動:

  • 商業對資料的評估
  • 立法要求
  • 資訊公開後的後果

許多複雜性來自於立法的不統一,不僅在國際上,而且在國內。例如,在歐洲,所有國家都已經批准了GDPR(通用資料保護條例),並且越來越多的國家採用了類別似的立法(例如,澳大利亞)。但是在歐盟內部,一些國家有額外的立法,因此僅僅遵守GDPR可能是不夠的。在美國,控制既有聯邦層面的,也有州層面的,加利福尼亞州已經採取了領先地位並採用了類別似GDPR的立法,但並非所有州都遵循了這一點。

GDPR 的核心原則

鑑於GDPR似乎是許多人的起點,值得檢視它試圖實作什麼以及它可能產生的影響。其核心原則包括:

  • 合法性、公平性和透明度原則
  • 目的限制原則
  • 資料最小化原則
  • 準確性原則
  • 儲存限制原則
  • 積分性和保密性原則
  • 責任原則

這些原則延伸到我們保留資料的個人的若干權利:

  • 個人有權知道為什麼保留他們的個人資料以及將用於什麼。
  • 個人有權要求檢視關於他們儲存的資訊。
  • 個人可以要求糾正資料中的任何不準確之處。
  • 個人可以行使「被遺忘的權利」,這將意味著所有資料都被刪除。
  • 個人可以要求限制使用他們的個人資料。

如何保護日誌中的敏感資料

為了減少日誌中敏感資料的風險,我們需要在可能的情況下最小化日誌中的敏感資料。如果需要保留敏感資料,那麼盡可能將其分開儲存。當無法分開儲存時,不要在日誌資料來源和最終安全目的地之間使用暫存日誌。就Fluentd而言,這意味著透過使用加密等手段來保護「傳輸中的資料」。

// 使用Fluentd處理日誌事件,去除敏感資料
const fluentd = require('fluent-logger');
const logger = fluentd.createLogger('myapp', {
  host: 'localhost',
  port: 24224,
  timeout: 3.0,
});

// 定義一個函式來過濾敏感資料
function filterSensitiveData(data) {
  // 實作過濾邏輯,例如刪除或遮蔽敏感欄位
  delete data.sensitiveField;
  return data;
}

// 使用過濾函式記錄日誌
logger.post('myapp.log', filterSensitiveData({ message: 'example', sensitiveField: 'secret' }));

內容解密:

上述程式碼展示瞭如何使用Fluentd的日誌處理功能來去除敏感資料。首先,我們建立了一個Fluentd記錄器例項,並定義了一個filterSensitiveData函式來過濾掉敏感欄位。然後,在記錄日誌時,我們使用這個函式來處理日誌資料,以確保敏感資訊不會被記錄。

日誌最佳實踐中的安全與合規考量

在現代化的技術架構中,日誌(Logging)扮演著至關重要的角色。它不僅幫助開發者與維運團隊進行問題排查,也在安全監控、合規性檢查等方面發揮關鍵作用。然而,日誌的處理與儲存也伴隨著諸多挑戰,尤其是在資料安全與隱私保護方面。

日誌安全的重要性

日誌中可能包含敏感資訊,例如個人身份識別資訊(PII)、財務資料或臨床資料。這些資訊若未妥善保護,可能導致嚴重的隱私洩露或經濟損失。因此,確保日誌的安全性是每家企業必須面對的課題。

敏感資料的定義

根據不同的法規與標準,敏感資料的定義也有所不同。一般而言,以下資料被視為敏感:

  • 能夠唯一識別個人的資料,如個人地址或社會保險號碼(PII)。
  • 可能對組織或個人造成財務影響的資料,如信用卡資訊、銀行帳戶或財務報告。
  • 與個人相關的臨床資料。

法規遵循與日誌管理

除了歐盟的通用資料保護條例(GDPR)外,許多國家和地區都有各自的資料保護法律。例如,美國的《健康保險可攜性和責任法案》(HIPAA)、日本的《金融商品交易法》(J-SOX)等,都對日誌的處理提出了具體要求。

支付卡行業資料安全標準(PCI DSS)是另一個重要的規範,它要求處理支付卡資訊的企業必須採取嚴格的安全措施,包括對日誌的保護。

日誌結構與格式的重要性

透過結構化的日誌格式,可以使日誌資訊更易於被解析和利用。例如,當資料函式庫連線發生錯誤時,結構化的日誌事件可以讓相關團隊快速接收到錯誤程式碼和資料函式庫詳細資訊,從而及時採取行動。

結構化日誌範例

{
  "timestamp": "2023-10-01T12:00:00Z",
  "level": "ERROR",
  "message": "Database connection failed",
  "database": {
    "host": "db.example.com",
    "port": 5432,
    "error_code": "CONN-001"
  }
}

程式碼範例:使用 Fluentd 解析結構化日誌

import json

def parse_log_event(log_event):
    try:
        event = json.loads(log_event)
        if event['level'] == 'ERROR' and 'database' in event:
            error_code = event['database']['error_code']
            # 傳送訊息至資料函式庫團隊
            send_alert(error_code, event['database'])
    except json.JSONDecodeError as e:
        print(f"Failed to parse log event: {e}")

# 假設這是從日誌檔案中讀取的日誌事件
log_event = '{"timestamp": "2023-10-01T12:00:00Z", "level": "ERROR", "message": "Database connection failed", "database": {"host": "db.example.com", "port": 5432, "error_code": "CONN-001"}}'
parse_log_event(log_event)

內容解密:

  1. parse_log_event 函式:此函式負責解析傳入的日誌事件,首先嘗試將日誌事件從 JSON 格式轉換為 Python 物件。如果解析成功且日誌級別為 ERROR,並且包含 database 資訊,則提取錯誤程式碼並傳送警示至相關團隊。
  2. 錯誤處理:如果 JSON 解析失敗,會捕捉 JSONDecodeError 例外並輸出錯誤訊息,以確保程式不會因為不合法的 JSON 字串而當機。
  3. send_alert 函式:這是一個虛擬函式,用於表示向資料函式庫團隊傳送警示的動作。在實際應用中,這個函式應該被替換為真正實作警示機制的程式碼,例如透過電子郵件、簡訊或內部通訊系統傳送通知。

風險管理的類別比

處理敏感資料的風險可以比喻為處理有毒物質。資料的敏感程度就像毒液的毒性,而資料量的大小則如同毒液的數量。透過適當的裝置、環境和專業知識,可以將風險降至最低。