1313# limitations under the License.
1414from __future__ import annotations
1515
16+ import base64
1617import datetime
1718import json
1819import logging
1920import urllib .parse
2021from typing import Any , Mapping , Optional , Sequence
2122
2223import google .auth
23- from google .api .monitored_resource_pb2 import MonitoredResource # type: ignore
24+ from google .api .monitored_resource_pb2 import ( # pylint: disable = no-name-in-module
25+ MonitoredResource ,
26+ )
2427from google .cloud .logging_v2 .services .logging_service_v2 import (
2528 LoggingServiceV2Client ,
2629)
2932)
3033from google .cloud .logging_v2 .types .log_entry import LogEntry
3134from google .cloud .logging_v2 .types .logging import WriteLogEntriesRequest
32- from google .logging .type .log_severity_pb2 import LogSeverity # type: ignore
33- from google .protobuf .struct_pb2 import Struct
34- from google .protobuf .timestamp_pb2 import Timestamp
35+ from google .logging .type .log_severity_pb2 import ( # pylint: disable = no-name-in-module
36+ LogSeverity ,
37+ )
38+ from google .protobuf .struct_pb2 import ( # pylint: disable = no-name-in-module
39+ Struct ,
40+ )
41+ from google .protobuf .timestamp_pb2 import ( # pylint: disable = no-name-in-module
42+ Timestamp ,
43+ )
3544from opentelemetry .exporter .cloud_logging .version import __version__
3645from opentelemetry .resourcedetector .gcp_resource_detector ._mapping import (
3746 get_monitored_resource ,
94103
95104
96105def convert_any_value_to_string (value : Any ) -> str :
97- t = type (value )
98- if t is bool or t is int or t is float or t is str :
106+ if isinstance (value , bool ):
107+ return "true" if value else "false"
108+ if isinstance (value , bytes ):
109+ return base64 .b64encode (value ).decode ()
110+ if (
111+ isinstance (value , int )
112+ or isinstance (value , float )
113+ or isinstance (value , str )
114+ ):
99115 return str (value )
100- if t is list or t is tuple :
116+ if isinstance ( value , list ) or isinstance ( value , tuple ) :
101117 return json .dumps (value )
102- logging .warning ("Unknown type %s found, cannot convert to string." , t )
118+ logging .warning (
119+ "Unknown value %s found, cannot convert to string." , type (value )
120+ )
103121 return ""
104122
105123
@@ -132,6 +150,7 @@ def export(self, batch: Sequence[LogData]):
132150 now = datetime .datetime .now ()
133151 log_entries = []
134152 for log_data in batch :
153+ log_entry = LogEntry ()
135154 log_record = log_data .log_record
136155 attributes = log_record .attributes or {}
137156 project_id = str (
@@ -144,18 +163,7 @@ def export(self, batch: Sequence[LogData]):
144163 )
145164 )
146165 )
147- monitored_resource_data = get_monitored_resource (
148- log_record .resource or Resource ({})
149- )
150- # convert it to proto
151- monitored_resource : Optional [MonitoredResource ] = (
152- MonitoredResource (
153- type = monitored_resource_data .type ,
154- labels = monitored_resource_data .labels ,
155- )
156- if monitored_resource_data
157- else None
158- )
166+ log_entry .log_name = f"projects/{ project_id } /logs/{ log_suffix } "
159167 # If timestamp is unset fall back to observed_time_unix_nano as recommended,
160168 # see https://github.com/open-telemetry/opentelemetry-proto/blob/4abbb78/opentelemetry/proto/logs/v1/logs.proto#L176-L179
161169 ts = Timestamp ()
@@ -165,12 +173,15 @@ def export(self, batch: Sequence[LogData]):
165173 )
166174 else :
167175 ts .FromDatetime (now )
168- log_name = f"projects/{ project_id } /logs/{ log_suffix } "
169- log_entry = LogEntry ()
170176 log_entry .timestamp = ts
171- log_entry .log_name = log_name
172- if monitored_resource :
173- log_entry .resource = monitored_resource
177+ monitored_resource_data = get_monitored_resource (
178+ log_record .resource or Resource ({})
179+ )
180+ if monitored_resource_data :
181+ log_entry .resource = MonitoredResource (
182+ type = monitored_resource_data .type ,
183+ labels = monitored_resource_data .labels ,
184+ )
174185 log_entry .trace_sampled = (
175186 log_record .trace_flags is not None
176187 and log_record .trace_flags .sampled
@@ -190,30 +201,27 @@ def export(self, batch: Sequence[LogData]):
190201 k : convert_any_value_to_string (v )
191202 for k , v in attributes .items ()
192203 }
193- if isinstance (log_record .body , Mapping ):
194- s = Struct ()
195- s .update (log_record .body )
196- log_entry .json_payload = s
197- elif type (log_record .body ) is bytes :
198- json_str = log_record .body .decode ("utf8" )
199- json_dict = json .loads (json_str )
200- if isinstance (json_dict , Mapping ):
201- s = Struct ()
202- s .update (json_dict )
203- log_entry .json_payload = s
204- else :
205- logging .warning (
206- "LogRecord.body was bytes type and json.loads turned body into type %s, expected a dictionary." ,
207- type (json_dict ),
208- )
209- else :
210- log_entry .text_payload = convert_any_value_to_string (
211- log_record .body
212- )
204+ self ._set_payload_in_log_entry (log_entry , log_record .body )
213205 log_entries .append (log_entry )
214206
215207 self ._write_log_entries (log_entries )
216208
209+ def _set_payload_in_log_entry (self , log_entry : LogEntry , body : Any | None ):
210+ struct = Struct ()
211+ if isinstance (body , Mapping ):
212+ struct .update (body )
213+ log_entry .json_payload = struct
214+ elif isinstance (body , bytes ):
215+ json_str = body .decode ("utf-8" , errors = "replace" )
216+ json_dict = json .loads (json_str )
217+ if isinstance (json_dict , Mapping ):
218+ struct .update (json_dict )
219+ log_entry .json_payload = struct
220+ else :
221+ log_entry .text_payload = base64 .b64encode (body ).decode ()
222+ else :
223+ log_entry .text_payload = convert_any_value_to_string (body )
224+
217225 def _write_log_entries (self , log_entries : list [LogEntry ]):
218226 batch : list [LogEntry ] = []
219227 batch_byte_size = 0
0 commit comments