Knative serving에서 로그 로깅 및 보기

이 페이지에서는 Knative serving을 사용할 때 사용할 수 있는 로그와 로그를 보고 작성하는 방법을 설명합니다.

Knative serving에는 두 가지 유형의 로그가 있습니다.

  • 요청 로그: Knative serving 서비스로 전송된 요청의 로그입니다. 이러한 로그는 자동으로 생성됩니다.
  • 컨테이너 로그: 컨테이너 로그 작성에 설명된 대로 지원되는 위치에 작성된 컨테이너 인스턴스(일반적으로 자체 코드)에서 내보낸 로그입니다.

로그 사용 설정

Google Cloud의 경우 로그가 자동으로 Cloud Logging으로 전송됩니다. Google Distributed Cloud의 경우 먼저 로그를 사용 설정해야 합니다.

로그 보기

다음 두 가지 방법으로 서비스의 로그를 볼 수 있습니다.

  • Google Cloud 콘솔에서 Knative serving 페이지 사용
  • Google Cloud 콘솔에서 Cloud Logging 로그 탐색기 사용

두 방법 모두 Cloud Logging에 저장된 동일한 로그를 검사하지만 Cloud Logging 로그 탐색기는 세부정보와 더 많은 필터링 기능을 제공합니다.

Knative serving에서 로그 보기

Knative serving 페이지에서 로그를 보려면 다음 안내를 따르세요.

  1. Knative serving으로 이동합니다.

  2. 표시되는 목록에서 원하는 서비스를 클릭합니다.

  3. 로그 탭을 클릭하여 이 서비스의 모든 버전에 대한 요청 및 컨테이너 로그를 가져옵니다. 로그 심각도 수준으로 필터링할 수 있습니다.

Cloud Logging에서 로그 보기

Cloud Logging 로그 탐색기에서 Knative serving 로그를 보려면 다음 안내를 따르세요.

  1. Google Cloud 콘솔의 로그 탐색기로 이동합니다.

  2. 페이지 상단에서 기존 Google Cloud 프로젝트를 선택하거나 새 프로젝트를 만듭니다.

  3. 드롭다운 메뉴를 사용하여 Kubernetes 컨테이너를 리소스로 선택합니다.

자세한 내용은 로그 탐색기 사용을 참조하세요.

Cloud Code에서 로그 보기

Cloud Code에서 로그를 보려면 IntelliJVisual Studio Code 가이드를 참조하세요.

프로그래매틱 방식으로 로그 읽기

프로그래매틱 방식으로 로그를 읽으려면 다음 방법 중 하나를 사용하면 됩니다.

컨테이너 로그 작성

서비스에서 로그를 작성하면 로그가 다음 위치 중 한 곳에 작성되는 한 Cloud Logging에서 자동으로 감지됩니다.

대부분의 개발자는 표준 출력 및 표준 오류를 사용하여 로그를 작성할 수 있어야 합니다.

이러한 지원되는 위치에 작성된 컨테이너 로그는 Knative serving 서비스, 버전, 위치와 자동으로 연결됩니다.

로그에서 단순 텍스트와 구조화된 JSON 사용 비교

로그를 작성할 때 간단한 텍스트 문자열을 전송하거나 한 줄의 직렬화된 JSON('구조화된' 데이터라도 함)을 보낼 수 있습니다. 이 구조화된 JSON은 Cloud Logging에서 감지 후 파싱하여 jsonPayload에 배치합니다. 이에 반해 간단한 텍스트 메시지는 textPayload에 배치됩니다.

구조화된 로그 작성

다음 스니펫은 구조화된 로그 항목을 작성하는 방법을 보여줍니다. 또한 로그 메시지와 해당 요청 로그의 상관관계를 지정하는 방법을 보여줍니다.

Node.js


// Uncomment and populate this variable in your code:
// const project = 'The project ID of your function or Cloud Run service';

// Build structured log messages as an object.
const globalLogFields = {};

// Add log correlation to nest all log messages beneath request log in Log Viewer.
// (This only works for HTTP-based invocations where `req` is defined.)
if (typeof req !== 'undefined') {
  const traceHeader = req.header('X-Cloud-Trace-Context');
  if (traceHeader && project) {
    const [trace] = traceHeader.split('/');
    globalLogFields['logging.googleapis.com/trace'] =
      `projects/${project}/traces/${trace}`;
  }
}

// Complete a structured log entry.
const entry = Object.assign(
  {
    severity: 'NOTICE',
    message: 'This is the default display field.',
    // Log viewer accesses 'component' as 'jsonPayload.component'.
    component: 'arbitrary-property',
  },
  globalLogFields
);

// Serialize to a JSON string and output.
console.log(JSON.stringify(entry));

Python

# Uncomment and populate this variable in your code:
# PROJECT = 'The project ID of your Cloud Run service';

# Build structured log messages as an object.
global_log_fields = {}

# Add log correlation to nest all log messages.
# This is only relevant in HTTP-based contexts, and is ignored elsewhere.
# (In particular, non-HTTP-based Cloud Functions.)
request_is_defined = "request" in globals() or "request" in locals()
if request_is_defined and request:
    trace_header = request.headers.get("X-Cloud-Trace-Context")

    if trace_header and PROJECT:
        trace = trace_header.split("/")
        global_log_fields[
            "logging.googleapis.com/trace"
        ] = f"projects/{PROJECT}/traces/{trace[0]}"

# Complete a structured log entry.
entry = dict(
    severity="NOTICE",
    message="This is the default display field.",
    # Log viewer accesses 'component' as jsonPayload.component'.
    component="arbitrary-property",
    **global_log_fields,
)

print(json.dumps(entry))

Go

각 로그 항목의 구조는 Entry 유형으로 제공됩니다.


// Entry defines a log entry.
type Entry struct {
	Message  string `json:"message"`
	Severity string `json:"severity,omitempty"`
	Trace    string `json:"logging.googleapis.com/trace,omitempty"`

	// Logs Explorer allows filtering and display of this as `jsonPayload.component`.
	Component string `json:"component,omitempty"`
}

// String renders an entry structure to the JSON format expected by Cloud Logging.
func (e Entry) String() string {
	if e.Severity == "" {
		e.Severity = "INFO"
	}
	out, err := json.Marshal(e)
	if err != nil {
		log.Printf("json.Marshal: %v", err)
	}
	return string(out)
}

Entry 구조체가 로깅되면 String 메서드가 호출되어 Cloud Logging에서 예상하는 JSON 형식으로 마샬링합니다.


func init() {
	// Disable log prefixes such as the default timestamp.
	// Prefix text prevents the message from being parsed as JSON.
	// A timestamp is added when shipping logs to Cloud Logging.
	log.SetFlags(0)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	// Uncomment and populate this variable in your code:
	// projectID = "The project ID of your Cloud Run service"

	// Derive the traceID associated with the current request.
	var trace string
	if projectID != "" {
		traceHeader := r.Header.Get("X-Cloud-Trace-Context")
		traceParts := strings.Split(traceHeader, "/")
		if len(traceParts) > 0 && len(traceParts[0]) > 0 {
			trace = fmt.Sprintf("projects/%s/traces/%s", projectID, traceParts[0])
		}
	}

	log.Println(Entry{
		Severity:  "NOTICE",
		Message:   "This is the default display field.",
		Component: "arbitrary-property",
		Trace:     trace,
	})

	fmt.Fprintln(w, "Hello Logger!")
}

자바

logback.xml 구성에서 Logstash JSON 인코더를 사용 설정하여 LogbackSLF4J로 JSON 로깅을 사용 설정합니다.

// Build structured log messages as an object.
Object globalLogFields = null;

// Add log correlation to nest all log messages beneath request log in Log Viewer.
// TODO(developer): delete this code if you're creating a Cloud
//                  Function and it is *NOT* triggered by HTTP.
String traceHeader = req.headers("x-cloud-trace-context");
if (traceHeader != null && project != null) {
  String trace = traceHeader.split("/")[0];
  globalLogFields =
      kv(
          "logging.googleapis.com/trace",
          String.format("projects/%s/traces/%s", project, trace));
}
// -- End log correlation code --

// Create a structured log entry using key value pairs.
// For instantiating the "logger" variable, see
// https://cloud.google.com/run/docs/logging#run_manual_logging-java
logger.error(
    "This is the default display field.",
    kv("component", "arbitrary-property"),
    kv("severity", "NOTICE"),
    globalLogFields);
<configuration>
  <appender name="jsonConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder">
      <!-- Ignore default logging fields -->
      <fieldNames>
        <timestamp>[ignore]</timestamp>
        <version>[ignore]</version>
        <logger>[ignore]</logger>
        <thread>[ignore]</thread>
        <level>[ignore]</level>
        <levelValue>[ignore]</levelValue>
      </fieldNames>
    </encoder>
  </appender>
  <root level="INFO">
    <appender-ref ref="jsonConsoleAppender"/>
  </root>
</configuration>

메시지의 특수 JSON 필드

구조화된 로그를 JSON 사전으로 제공하면 일부 특수 필드가 jsonPayload에서 삭제되고 특수 필드 문서에 설명된 대로 생성된 LogEntry의 해당 필드에 작성됩니다.

예를 들어 JSON에 severity 속성이 포함되어 있으면 이 속성은 jsonPayload에서 삭제되고 대신 로그 항목의 severity로 표시됩니다. message 속성이 있으면 이 속성은 로그 항목의 기본 표시 텍스트로 사용됩니다. 특수 속성에 대한 자세한 내용은 아래의 Logging 리소스 섹션을 참조하세요.

컨테이너 로그와 요청 로그의 상관관계

로그 탐색기에서 trace가 동일하여 상관관계가 있는 여러 로그 항목을 '상위-하위' 형식으로 조회할 수 있습니다. 요청 로그 항목의 왼쪽에 있는 삼각형 아이콘을 클릭하면 해당 요청과 관련된 컨테이너 로그가 요청 로그 아래에 중첩되어 표시됩니다.

Cloud Logging 클라이언트 라이브러리를 사용하는 경우를 제외하고 컨테이너 로그와 요청 로그의 상관관계는 자동으로 지정되지 않습니다. 클라이언트 라이브러리를 사용하지 않고 컨테이너 로그와 요청 로그의 상관관계를 지정하려면 위의 구조화된 로깅 샘플에 표시된 대로 X-Cloud-Trace-Context 헤더에서 추출된 trace 식별자가 있는 logging.googleapis.com/trace 필드를 포함하는 구조화된 JSON 로그 줄을 사용할 수 있습니다.

요청 로그 리소스 사용량 제어

요청 로그는 자동으로 생성됩니다. Knative serving에서 직접 요청 로그의 양을 제어할 수 없지만 Cloud Logging의 로그 제외 기능을 사용할 수 있습니다.

Logging 에이전트에 대한 참고사항

Compute Engine과 같은 특정 Google Cloud 제품에서 Cloud Logging을 사용한 경우 Cloud Logging 로깅 에이전트를 사용했을 수 있습니다. Knative serving은 기본적으로 로그 수집을 지원하므로 Logging 에이전트를 사용하지 않습니다.

Logging 리소스

로그 탐색기에서 로그 항목을 클릭하면 JSON 형식의 로그 항목이 열리고 원하는 세부정보를 확인할 수 있습니다.

타임스탬프, 심각도, httpRequest와 같은 로그 항목의 모든 필드는 표준이며 로그 항목 문서에 설명되어 있습니다.

하지만 Knative serving에 중요한 라벨 또는 리소스 라벨도 몇 가지 있으며, 이러한 라벨은 샘플 콘텐츠와 함께 아래에 나와 있습니다.

{
 httpRequest: {}
 insertId:  "5c82b3d1000ece0000000000"
 labels: {
  instanceId:  "00bf4bf00000fb59c906a00000c9e29c2c4e06dce91500000000056008d2b6460f163c0057b97b2345f2725fb2423ee5f0bafd36df887fdb1122371563cf1ff453717282afe000001"
 }
 logName:  "projects/my-project/logs/kubernetes-engine/enterprise/knative-serving/.googleapis.com%2Frequests"
 receiveTimestamp:  "2019-03-08T18:26:25.981686167Z"
 resource: {
  labels: {
   configuration_name:  "myservice"
   location:  "us-central1"
   project_id:  "my-project"
   revision_name:  "myservice-00002"
   service_name:  "myservice"
  }
  type:  "cloud_run_revision"
 }
 severity:  "INFO"
 timestamp:  "2019-03-08T18:26:25.970397Z"
}
필드 값 및 참고
instanceId 요청을 처리한 컨테이너 인스턴스입니다.
logName 요청 로그, 표준 오류, 표준 출력 등의 로그를 식별합니다.
configuration_name 요청을 제공한 버전을 만든 구성 리소스입니다.
location 서비스의 GCP 위치를 식별합니다.
project_id 서비스가 배포되는 프로젝트입니다.
revision_name 요청을 제공한 버전입니다.
service_name 요청을 제공한 서비스입니다.
type cloud_run_revision. Knative serving 리소스 유형입니다.