在 python 记录器中执行相当于 log_struct 的操作

Doing the equivalent of log_struct in python logger

在 google 示例中,它给出了以下内容:

logger.log_struct({
    'message': 'My second entry',
    'weather': 'partly cloudy',
})

我如何在 python 的记录器中做同样的事情。例如:

import logging
log.info(
    msg='My second entry', 
    extra = {'weather': "partly cloudy"}
)

当我在 stackdriver 中查看时,额外的字段没有得到正确解析:

2018-11-12 15:41:12.366 PST
My second entry

Expand all | Collapse all 

{
 insertId:  "1de1tqqft3x3ri"  
 jsonPayload: {
  message:  "My second entry"   
  python_logger:  "Xhdoo8x"   
 }
 logName:  "projects/Xhdoo8x/logs/python"  
 receiveTimestamp:  "2018-11-12T23:41:12.366883466Z"  
 resource: {…}  
 severity:  "INFO"  
 timestamp:  "2018-11-12T23:41:12.366883466Z"  
}

我该怎么做?

我现在最接近的是:

log.handlers[-1].client.logger('').log_struct("...")

但这仍然需要第二次调用...

这目前不可能,see this open feature request on google-cloud-python了解更多详情。

当前解决方案:

更新 1 - 用户 Seth Nickell improved my proposed solution,所以我更新了这个答案,因为他的方法更出色。以下是根据他在GitHub的回复:

https://github.com/snickell/google_structlog

pip install google-structlog

这样使用:

import google_structlog

google_structlog.setup(log_name="here-is-mylilapp")

# Now you can use structlog to get searchable json details in stackdriver...
import structlog
logger = structlog.get_logger()
logger.error("Uhoh, something bad did", moreinfo="it was bad", years_back_luck=5)

# Of course, you can still use plain ol' logging stdlib to get { "message": ... } objects
import logging
logger = logging.getLogger("yoyo")
logger.error("Regular logging calls will work happily too")

# Now you can search stackdriver with the query:
# logName: 'here-is-mylilapp'

原回答:

基于an answer from this GitHub thread,我使用以下命令将自定义对象记录为信息负载。它从原始 _Worker.enqueue 派生出更多内容,并支持传递自定义字段。

from google.cloud.logging import _helpers
from google.cloud.logging.handlers.transports.background_thread import _Worker

def my_enqueue(self, record, message, resource=None, labels=None, trace=None, span_id=None):
    queue_entry = {
        "info": {"message": message, "python_logger": record.name},
        "severity": _helpers._normalize_severity(record.levelno),
        "resource": resource,
        "labels": labels,
        "trace": trace,
        "span_id": span_id,
        "timestamp": datetime.datetime.utcfromtimestamp(record.created),
    }

    if 'custom_fields' in record:
        entry['info']['custom_fields'] = record.custom_fields

    self._queue.put_nowait(queue_entry)

_Worker.enqueue = my_enqueue

然后

import logging
from google.cloud import logging as google_logging

logger = logging.getLogger('my_log_client')
logger.addHandler(CloudLoggingHandler(google_logging.Client(), 'my_log_client'))

logger.info('hello', extra={'custom_fields':{'foo': 1, 'bar':{'tzar':3}}})

导致:

这使得根据这些 custom_fields.

进行过滤变得更加容易

让我们承认这不是一个好的编程,但在正式支持此功能之前似乎没有太多其他可以做的事情。

官方文档:Setting Up Cloud Logging for Python

您可以从 Python 个应用程序向 Logging 写入日志

  1. 通过使用记录客户端库中包含的 Python 记录处理程序,或者
  2. 直接使用 Cloud Logging API 云客户端库 Python。

我没有得到 Python 日志记录模块来导出 json 有效负载,但是 Python 的云日志记录库可以工作:

google-cloud-logging >= v.3.0.0 可以做到

不再需要 Python 日志解决方案,您唯一需要的是安装 Python >= 3.6 和

pip install google-cloud-logging

然后,您可以使用 Python 日志记录

import os

# have the environment variable ready:
# GOOGLE_APPLICATION_CREDENTIALS
# Then:
GOOGLE_APPLICATION_CREDENTIALS = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")
client = google.cloud.logging.Client.from_service_account_json(
    GOOGLE_APPLICATION_CREDENTIALS
)
log_name = "test"
gcloud_logger = client.logger(log_name)

# jsonPayloads
gcloud_logger.log_struct(entry)
# messages
gcloud_logger.log_text('hello')
# generic (can find out whether it is jsonPayload or message)!
gcloud_logger.log(entry or 'hello')

您 运行 在 GCP 外部的 Python 文件中执行这些命令并通过或多或少的 one-liner 到达 GCP,您只需要凭据。

您甚至可以使用 gcloud 记录器一次性打印和记录,摘自 Writing structured logs,未经测试。

Python 记录以将 json 有效负载记录到 GCP 日志中 (TL/DR)

我无法将此发送到 运行!

您也可以使用 Python 的 built-in 日志记录模块和其他答案中提到的解决方法,但我没有得到 运行。 如果您直接将字典或其 json.dumps() 作为参数传递,它将不起作用,从那时起,您将获得整个字典的字符串输出,您无法将其读取为 json 树。 但是当我使用 logger.info() 在名为 extras.

的示例参数中记录 jsonPayload / json.dumps 时,它对我也不起作用
import json
import os

#...

# https://googleapis.dev/python/logging/latest/stdlib-usage.html
GOOGLE_APPLICATION_CREDENTIALS = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")
client = google.cloud.logging.Client.from_service_account_json(
    GOOGLE_APPLICATION_CREDENTIALS
)
log_name = "test"
handler = CloudLoggingHandler(client, name=log_name)
setup_logging(handler)
logger = logging.getLogger()
logger.setLevel(logging.INFO)  # Set default level.

#...

    # 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"
    )

    # Python logging to log jsonPayload into GCP logs
    logger.info('hello', extras=json.dumps(entry))

我也尝试了另一个答案的 google-structlog 解决方案,只抛出错误:

google_structlog.setup(log_name=log_name) TypeError: setup() got an unexpected keyword argument 'log_name'

我使用了 Python v3.10.2 和

google-auth==1.35.0
google-cloud-core==1.7.2
google-cloud-logging==1.15.0
googleapis-common-protos==1.54.0

研究步骤 gcloud logging (TL/DR)

在 github at googleapis / python-logging 上解决了已解决的问题(请参阅最后的合并和关闭): Logging: support sending structured logs to stackdriver via stdlib 'logging'. #13

你找到 feat!: support json logs #316 :

This PR adds full support for JSON logs, along with standard text logs. Now, users can call logging.error({'a':'b'}), and they will get a JsonPayload in Cloud Logging, Or call logging.error('test') to receive a TextPayload

As part of this change, I added a generic logger.log() function, which serves as a generic entry-point instead of logger.log_text or logger.log_struct. It will infer which log function is meant based on the input type

Previously, the library would attach the python logger name as part of the JSON payload for each log. Now, that information will be attached as a label instead, giving users full control of the log payload fields

Fixes #186, #263, #13

主要新版本列出了新功能:

chore(main): release 3.0.0 #473