Created
January 10, 2025 19:53
-
-
Save erik4github/33d0b755bad0427016075db2ba8dd406 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
logger = Logger( | |
service="my-lambda-service", # or any service name | |
utc=True, | |
# Setting formatter_cls doesn't exist directly—pass an instance as below | |
logger_formatter=OtelJsonFormatter(), | |
# log_level="INFO", # optionally set a log level | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import json | |
import os | |
import logging | |
import time | |
from typing import Any, Dict | |
from aws_lambda_powertools import Logger | |
from aws_lambda_powertools.logging.formatter import JsonFormatter | |
def _map_python_level_to_severity_number(levelno: int) -> int: | |
"""Map Python's logging level numbers to an approximate OTel severity_number.""" | |
# Common mapping, can be adjusted | |
if levelno <= logging.DEBUG: | |
return 5 # TRACE or DEBUG range | |
elif levelno <= logging.INFO: | |
return 9 # INFO | |
elif levelno <= logging.WARNING: | |
return 13 # WARN | |
elif levelno <= logging.ERROR: | |
return 17 # ERROR | |
else: | |
return 21 # FATAL | |
class OtelJsonFormatter(JsonFormatter): | |
""" | |
Custom JSON formatter to produce logs that align with OpenTelemetry log data model. | |
""" | |
def format(self, record: logging.LogRecord) -> str: | |
# Default attributes from the record | |
log_message = record.getMessage() # main log string | |
level_text = record.levelname # e.g. INFO, DEBUG, ERROR | |
level_number = _map_python_level_to_severity_number(record.levelno) | |
# The timestamp when the record was created, in RFC3339 or epoch | |
# record.created is float seconds since epoch. Convert to milliseconds or ISO 8601. | |
# For OTel logs, you can use an RFC3339/ISO 8601. We'll do an ISO8601 here. | |
timestamp = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(record.created)) \ | |
+ f".{int((record.created - int(record.created)) * 1_000_000):06d}Z" | |
# Build the resource block. In a Lambda, you can capture function name, region, etc. | |
resource = { | |
"cloud.provider": "aws", | |
"cloud.platform": "aws_lambda", | |
"cloud.region": os.environ.get("AWS_REGION"), | |
"faas.name": os.environ.get("AWS_LAMBDA_FUNCTION_NAME"), | |
"faas.version": os.environ.get("AWS_LAMBDA_FUNCTION_VERSION"), | |
} | |
# If you want trace_id/span_id correlation from X-Ray, parse them from environment | |
# or from a custom context. This is a simplified example. | |
xray_trace_id = os.environ.get("_X_AMZN_TRACE_ID", "") | |
# A typical X-Amzn-Trace-Id looks like: | |
# Root=1-6483fcc6-2efd98a76b34c26a6609337e;Parent=1234567890abcdef;Sampled=1 | |
trace_id = None | |
span_id = None | |
if "Root=" in xray_trace_id and "Parent=" in xray_trace_id: | |
# Extract Root=1-xxxxxx-xxxxxxxxxxx | |
root_part = xray_trace_id.split("Root=")[1].split(";")[0] | |
# OTel’s 128-bit trace IDs do not match 1-xxxx-xxxx directly, | |
# but you can transform them for correlation. For brevity: | |
trace_id = root_part.replace("-", "") | |
parent_part = xray_trace_id.split("Parent=")[1].split(";")[0] | |
span_id = parent_part | |
# Additional fields you might store as attributes | |
# This merges any custom fields from the record's "extra" dictionary | |
# (i.e., `logger.info("...", extra={"key": "value"})`) | |
attributes: Dict[str, Any] = {} | |
if hasattr(record, "extra"): | |
attributes.update(record.extra) | |
# Construct the OpenTelemetry-like log record | |
otel_log = { | |
"timestamp": timestamp, | |
"severity_text": level_text, | |
"severity_number": level_number, | |
"body": log_message, | |
"attributes": attributes, | |
"resource": resource, | |
} | |
# Add trace info if present | |
if trace_id: | |
otel_log["trace_id"] = trace_id | |
if span_id: | |
otel_log["span_id"] = span_id | |
# Return as a JSON string | |
return json.dumps(otel_log, ensure_ascii=False) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment