惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
G
Google Developers Blog
aimingoo的专栏
aimingoo的专栏
博客园 - 聂微东
酷 壳 – CoolShell
酷 壳 – CoolShell
小众软件
小众软件
Stack Overflow Blog
Stack Overflow Blog
美团技术团队
博客园_首页
T
Tailwind CSS Blog
博客园 - 三生石上(FineUI控件)
B
Blog
D
DataBreaches.Net
腾讯CDC
C
Check Point Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
U
Unit 42
月光博客
月光博客
V
V2EX
Vercel News
Vercel News
T
The Blog of Author Tim Ferriss
The Cloudflare Blog
博客园 - 叶小钗
Y
Y Combinator Blog

jdhao's digital space

Conversion between base64 and OpenCV or PIL Image 腾讯云对象存储博客图床开启 CDN 加速(不需要购买额外域名) Search and Replace in Multiple Files in Vim/Neovim Change Table Column Width in LaTeX Image or Table Side by Side in LaTeX LaTeX 并排显示图像或表格 Firenvim: Neovim inside Your Browser Content inside HTML tags missing in Latest Hugo? Creating Markdown Front Matter with Ultisnips Labelme JSON 标注格式转 voc XML 格式 Nifty Nvim Techniques That Make My Life Easier -- Series 6 macOS 下如何为视频制作字幕 Running Command Asynchronously inside Neovim Resolving Merge Conflict after Git Stash Pop Pylint: command not found? A Hands-on Experience with Neovim's Built-in LSP Support How to Convert PDF to Images with Imagemagick 互联网上常用缩略语集锦 File Backup in Neovim Converting PDF Pages to Images with Poppler Nifty Nvim Techniques That Make My Life Easier -- Series 5 Neovim Configuration for System-wide Use How to sort a list of tuple or list in Python -- lambda or itemgetter? Building A Vim Statusline from Scratch 人类第一颗原子弹爆炸始末 Distributed Training in PyTorch with Horovod Learning Expect Programming Essential Knowledge about SSH Nifty LaTeX Techniques -- Series 1 更改 Adsense 邮寄地址,重新寄送 PIN
Make Python logging Work in GCP
2024-09-21 · via jdhao's digital space

When deploying a Python application to GCP Kuberntes Engine, we may see issues that the logging level is not correct in the cloud logging explorer. In order for the Python logging package to work with GCP, we need some additional configurations.

structured logging#

The GCP logging is using structured logging. The logging message from the Python default logging library is not structured. That is why we are using the google cloud logging client to reformat the logging messages. We can find the structure for the logEntry used by google cloud logging here.

ref:

Using the gcloud logging client#

Install the GCP logging client for Python is simple:

pip install --upgrade google-cloud-logging

The google-cloud-logging package will transform the log message to a format required by the Gcloud logging platform. It will work under the hood to make sure the log severity level is correct and also add other fields to the log message.

The official instruction from here tells us to do the following:

root_logger = logging.getLogger()
root_logger.setLevel(LOG_LEVEL)

gcloud_log_client = google.cloud.logging.Client()
gcloud_log_client.setup_logging(log_level=logging.DEBUG)

And in your actual code, you do something like this:

import logging

logger = logging.getLogger(__name__)

logger.info("this is an info message")

The setup_logging() method will add a custom handler from the gcloud logging package, so that the messages from the Python logging package is transformed to a format required by google cloud logging. When the application is running in (GKE), the following handler is added to the root logger

google.cloud.logging_v2.handlers.structured_log.StructuredLogHandler

When the application is running locally, the following handler is added to the root logger:

google.cloud.logging_v2.handlers.handlers.CloudLoggingHandler

The difference between CloudLoggingHandler and StructuredLogHandler can be found here.

However, this plain way has several potential issues:

  • The log message is not going be printed in the console, if you are developing the code locally
  • If you also add other handlers to the root logger, you will see duplicated logging messages in the GCP cloud logging explorer1.
  • You need to run gcloud auth application-default login to autenticate yourself when running the application locally.

Separate logging setup for local and GCP#

It is better to have separate setup for the root logger when running the code locally2. Something like this should suffice:

import logging
import google.cloud.logging

root_logger = logging.getLogger()
root_logger.setLevel(LOG_LEVEL)

formatter = logging.Formatter("%(name)s:%(lineno)d - %(levelname)s - %(message)s")

# running_in_gcp_k8s() check whether the code is running in GCP kubernetes engine (GKE)
if running_in_gcp_k8s():
    gcloud_log_client = google.cloud.logging.Client()
    gcloud_log_client.setup_logging(log_level=logging.DEBUG)
else:
    # for local running, use a plain stream handler
    stream_handler = logging.StreamHandler()
    stream_handler.setLevel(LOG_LEVEL)
    stream_handler.setFormatter(formatter)

    # add stream handler to root_logger
    root_logger.addHandler(stream_handler)

formatter for gcloud logging handler?#

By default, if you use the setup_logging() method above, only the plain message is printed. If we want to include logRecord attributes like module name, line number, level name etc, we need to define our own formatter.

import logging
import google.cloud.logging_v2

gcloud_log_client = google.cloud.logging.Client()
gcloud_log_client.setup_logging(log_level=logging.DEBUG)

formatter = logging.Formatter("%(name)s:%(lineno)d - %(levelname)s - %(message)s")
gcp_handler = [for h in logging.getLogger().handlers if isinstance(h, google.cloud.logging_v2.handlers.StructuredLogHandler)][0]
gcp_handler.setFormatter(formatter)

log extra field#

With cloud logging, we can also log extra meta data, like trace, labels etc.

logger.info(
    "logging test, info level with extra field",
    extra={"json_fields": {"key": "value"}, "trace": "1234", "labels": {"foo": "bar"}},
)

WTF is stackdriver logging?#

In short, it is an old name for Google cloud logging. There are more explanations in this post.

References#