在 Knative serving 中记录和查看日志

本页面介绍使用 Knative serving 时可用的日志,以及如何查看和写入日志。

Knative serving 有两种类型的日志:

  • 请求日志:用于记录发送到 Knative serving 服务的请求。这些日志是系统自动创建的。
  • 容器日志:这些日志由容器实例发出(通常来自您自己的代码),并将写入受支持的位置(请参阅写入容器日志)。

启用日志

对于 Google Cloud,日志会自动发送到 Cloud Logging。对于 Google Distributed Cloud,必须先启用日志

查看日志

您可以通过以下几种方式查看服务的日志:

  • 使用 Google Cloud 控制台中的 Knative serving 页面
  • 使用 Google Cloud 控制台中的 Cloud Logging Logs Explorer。

这两种查看方法都会检查 Cloud Logging 中存储的相同日志,但 Cloud Logging 日志浏览器提供了更多详细信息和更多过滤功能。

在 Knative serving 中查看日志

如需在 Knative serving 页面中查看日志,请执行以下操作:

  1. 前往 Knative serving

  2. 在显示的列表中点击所需的服务。

  3. 点击日志标签页,以获取此服务的所有修订版本的请求日志及容器日志。您可以按日志严重级别进行过滤。

查看 Cloud Logging 中的日志

如需在 Cloud Logging Logs Explorer 中查看 Knative serving 日志,请执行以下操作:

  1. 前往 Google Cloud 控制台中的日志浏览器页面。

  2. 在页面顶部选择一个现有 Google Cloud 项目,或者创建一个新项目。

  3. 使用下拉菜单选择 Kubernetes 容器资源。

如需了解详情,请参阅使用日志浏览器

在 Cloud Code 中查看日志

如需在 Cloud Code 中查看日志,请阅读 IntelliJVisual Studio Code 指南。

以编程方式读取日志

如果要以编程方式读取日志,可以使用以下方法之一:

写入容器日志

从服务中写入日志时,只要将日志写入以下任何位置,Cloud Logging 就会自动获取这些日志:

大多数开发者都应该使用标准输出和标准错误来写入日志。

写入这些受支持位置的容器日志会自动与 Knative serving 服务、修订版本和位置相关联。

在日志中使用简单文本和结构化 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 struct 时,调用 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!")
}

Java

通过在 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 中移除,并将其显示为日志条目的 severitymessage 属性用作日志条目的主显示文本(如果存在)。 如需详细了解特殊属性,请参阅下面的 Logging 资源部分。

将容器日志与请求日志相关联

在 Logs Explorer 中,以“父级-子级”格式可以查看由相同 trace 关联的日志:当您点击请求日志条目左侧的三角形图标时,与该请求相关的容器日志即会嵌套显示在请求日志下方。

如果不使用 Cloud Logging 客户端库,容器日志不会自动关联至请求日志。要在不使用客户端库的情况下将容器日志与请求日志相关联,您可以使用结构化 JSON 日志行,其包含 logging.googleapis.com/trace 字段,该字段具有从 X-Cloud-Trace-Context 标头中提取的跟踪标识符,如以上示例结构化日志记录所示。

控制请求日志资源用量

请求日志是系统自动创建的。虽然您无法直接通过 Knative serving 控制请求日志量,但您可以使用 Cloud Logging 的日志排除功能。

有关 Logging 代理的说明

如果您已将 Cloud Logging 用于某些 Google Cloud 产品(例如 Compute Engine),则可能已使用 Cloud Logging Logging 代理。Knative serving 不使用 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 Configuration 资源,用于创建处理请求的修订版本。
location 标识服务的 GCP 位置。
project_id 服务被部署到的项目。
revision_name 处理请求的修订版本。
service_name 处理请求的服务。
type cloud_run_revision。Knative serving 资源类型。