Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions opentelemetry-exporter-gcp-logging/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

## Unreleased

- Added support for when a `Mapping[str, bytes]` or `Mapping[str, List[bytes]]` is in `LogRecord.body`.
- Added support for when a `Mapping[str, List[Mapping]]` is in `LogRecord.body`.
- Added support for when a `Mapping[str, bytes]` or `Mapping[str, List[bytes]]` is in `LogRecord.body` or `LogRecord.attributes`.
- Added support for when a `Mapping[str, List[Mapping]]` is in `LogRecord.body` or `LogRecord.attributes`.
- Do not call `logging.warning` when `LogRecord.body` is of None type, instead leave `LogEntry.payload` empty.
- Update opentelemetry-api/sdk dependencies to 1.3.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import json
import logging
import re
import urllib.parse
from base64 import b64encode
from typing import Any, Mapping, MutableMapping, Optional, Sequence

import google.auth
Expand Down Expand Up @@ -106,16 +106,30 @@
INVALID_LOG_NAME_MESSAGE = "%s is not a valid log name. log name must be <512 characters and only contain characters: A-Za-z0-9/-_."


class _GenAiJsonEncoder(json.JSONEncoder):
def default(self, o: Any) -> Any:
if isinstance(o, bytes):
return b64encode(o).decode()
return super().default(o)


def _convert_any_value_to_string(value: Any) -> str:
if isinstance(value, bool):
return "true" if value else "false"
if isinstance(value, bytes):
return base64.b64encode(value).decode()
if isinstance(value, (int, float, str)):
return str(value)
if isinstance(value, (list, tuple)):
return json.dumps(value)
return ""
if isinstance(value, (list, tuple, Mapping)):
return json.dumps(value, separators=(",", ":"), cls=_GenAiJsonEncoder)
try:
return str(value)
except Exception as exc: # pylint: disable=broad-except
logging.exception(
"Error mapping AnyValue to string, this field will not be added to the LogEntry: %s",
exc,
)
return ""


# Be careful not to mutate original body. Make copies of anything that needs to change.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
"Ref": 164611595.0
},
"labels": {
"boolArray": "[true, false, true, true]",
"boolArray": "[true,false,true,true]",
"float": "25.43231",
"int": "25",
"intArray": "[21, 18, 23, 17]"
"intArray": "[21,18,23,17]"
},
"logName": "projects/fakeproject/logs/test",
"resource": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[
{
"entries": [
{
"labels": {
"a": "[{\"key\":\"Ynl0ZXM=\"}]",
"b": "[true,false,false,true]",
"c": "{\"a_dict\":\"abcd\",\"akey\":1234}",
"d": "{\"gen_ai.input.messages\":[{\"role\":\"user\",\"parts\":[{\"type\":\"text\",\"content\":\"Get weather details in New Delhi and San Francisco?\"}]},{\"role\":\"model\",\"parts\":[{\"type\":\"tool_call\",\"arguments\":{\"location\":\"New Delhi\"},\"name\":\"get_current_weather\",\"id\":\"get_current_weather_0\"},{\"type\":\"tool_call\",\"arguments\":{\"location\":\"San Francisco\"},\"name\":\"get_current_weather\",\"id\":\"get_current_weather_1\"}]},{\"role\":\"user\",\"parts\":[{\"type\":\"tool_call_response\",\"response\":{\"content\":\"{\\\"temperature\\\": 35, \\\"unit\\\": \\\"C\\\"}\"},\"id\":\"get_current_weather_0\"},{\"type\":\"tool_call_response\",\"response\":{\"content\":\"{\\\"temperature\\\": 25, \\\"unit\\\": \\\"C\\\"}\"},\"id\":\"get_current_weather_1\"}]}],\"gen_ai.system_instructions\":[{\"type\":\"text\",\"content\":\"You are a clever language model\"}],\"gen_ai.output.messages\":[{\"role\":\"model\",\"parts\":[{\"type\":\"text\",\"content\":\"The current temperature in New Delhi is 35\\u00b0C, and in San Francisco, it is 25\\u00b0C.\"}],\"finish_reason\":\"stop\"}]}"
},
"logName": "projects/fakeproject/logs/test",
"resource": {
"labels": {
"location": "global",
"namespace": "",
"node_id": ""
},
"type": "generic_node"
},
"timestamp": "2025-01-15T21:25:10.997977393Z"
}
],
"partialSuccess": true
}
]
158 changes: 91 additions & 67 deletions opentelemetry-exporter-gcp-logging/tests/test_cloud_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,74 @@

PROJECT_ID = "fakeproject"

GEN_AI_DICT = {
"gen_ai.input.messages": (
{
"role": "user",
"parts": (
{
"type": "text",
"content": "Get weather details in New Delhi and San Francisco?",
},
),
},
{
"role": "model",
"parts": (
{
"type": "tool_call",
"arguments": {"location": "New Delhi"},
"name": "get_current_weather",
"id": "get_current_weather_0",
},
{
"type": "tool_call",
"arguments": {"location": "San Francisco"},
"name": "get_current_weather",
"id": "get_current_weather_1",
},
),
},
{
"role": "user",
"parts": (
{
"type": "tool_call_response",
"response": {
"content": '{"temperature": 35, "unit": "C"}'
},
"id": "get_current_weather_0",
},
{
"type": "tool_call_response",
"response": {
"content": '{"temperature": 25, "unit": "C"}'
},
"id": "get_current_weather_1",
},
),
},
),
"gen_ai.system_instructions": (
{
"type": "text",
"content": "You are a clever language model",
},
),
"gen_ai.output.messages": (
{
"role": "model",
"parts": (
{
"type": "text",
"content": "The current temperature in New Delhi is 35°C, and in San Francisco, it is 25°C.",
},
),
"finish_reason": "stop",
},
),
}


def test_too_large_log_raises_warning(caplog) -> None:
client = LoggingServiceV2Client(credentials=AnonymousCredentials())
Expand Down Expand Up @@ -165,73 +233,7 @@ def test_convert_gen_ai_body(
log_record=LogRecord(
event_name="gen_ai.client.inference.operation.details",
timestamp=1736976310997977393,
body={
"gen_ai.input.messages": (
{
"role": "user",
"parts": (
{
"type": "text",
"content": "Get weather details in New Delhi and San Francisco?",
},
),
},
{
"role": "model",
"parts": (
{
"type": "tool_call",
"arguments": {"location": "New Delhi"},
"name": "get_current_weather",
"id": "get_current_weather_0",
},
{
"type": "tool_call",
"arguments": {"location": "San Francisco"},
"name": "get_current_weather",
"id": "get_current_weather_1",
},
),
},
{
"role": "user",
"parts": (
{
"type": "tool_call_response",
"response": {
"content": '{"temperature": 35, "unit": "C"}'
},
"id": "get_current_weather_0",
},
{
"type": "tool_call_response",
"response": {
"content": '{"temperature": 25, "unit": "C"}'
},
"id": "get_current_weather_1",
},
),
},
),
"gen_ai.system_instructions": (
{
"type": "text",
"content": "You are a clever language model",
},
),
"gen_ai.output.messages": (
{
"role": "model",
"parts": (
{
"type": "text",
"content": "The current temperature in New Delhi is 35°C, and in San Francisco, it is 25°C.",
},
),
"finish_reason": "stop",
},
),
},
body=GEN_AI_DICT,
),
instrumentation_scope=InstrumentationScope("test"),
)
Expand Down Expand Up @@ -303,3 +305,25 @@ def test_convert_various_types_of_bodies(
]
cloudloggingfake.exporter.export(log_data)
assert cloudloggingfake.get_calls() == snapshot_writelogentrycalls


def test_convert_various_types_of_attributes(
cloudloggingfake: CloudLoggingFake,
snapshot_writelogentrycalls: List[WriteLogEntriesCall],
) -> None:
log_data = [
LogData(
log_record=LogRecord(
attributes={
"a": [{"key": b"bytes"}],
"b": [True, False, False, True],
"c": {"a_dict": "abcd", "akey": 1234},
"d": GEN_AI_DICT,
},
timestamp=1736976310997977393,
),
instrumentation_scope=InstrumentationScope("test"),
)
]
cloudloggingfake.exporter.export(log_data)
assert cloudloggingfake.get_calls() == snapshot_writelogentrycalls