1919import static com .google .common .base .Preconditions .checkNotNull ;
2020
2121import com .google .cloud .MonitoredResource ;
22+ import com .google .cloud .logging .Payload .Type ;
2223import com .google .common .base .Function ;
2324import com .google .common .base .MoreObjects ;
2425import com .google .common .collect .ImmutableMap ;
26+ import com .google .gson .Gson ;
27+ import com .google .gson .GsonBuilder ;
28+ import com .google .gson .JsonElement ;
29+ import com .google .gson .JsonObject ;
30+ import com .google .gson .JsonPrimitive ;
31+ import com .google .gson .JsonSerializationContext ;
32+ import com .google .gson .JsonSerializer ;
2533import com .google .logging .v2 .LogEntryOperation ;
2634import com .google .logging .v2 .LogEntrySourceLocation ;
2735import com .google .logging .v2 .LogName ;
@@ -61,8 +69,8 @@ public LogEntry apply(com.google.logging.v2.LogEntry pb) {
6169 private final HttpRequest httpRequest ;
6270 private final Map <String , String > labels ;
6371 private final Operation operation ;
64- private final Object trace ;
65- private final Object spanId ;
72+ private final String trace ;
73+ private final String spanId ;
6674 private final boolean traceSampled ;
6775 private final SourceLocation sourceLocation ;
6876 private final Payload <?> payload ;
@@ -80,8 +88,8 @@ public static class Builder {
8088 private HttpRequest httpRequest ;
8189 private Map <String , String > labels = new HashMap <>();
8290 private Operation operation ;
83- private Object trace ;
84- private Object spanId ;
91+ private String trace ;
92+ private String spanId ;
8593 private boolean traceSampled ;
8694 private SourceLocation sourceLocation ;
8795 private Payload <?> payload ;
@@ -245,7 +253,7 @@ public Builder setTrace(String trace) {
245253 * relative resource name, the name is assumed to be relative to `//tracing.googleapis.com`.
246254 */
247255 public Builder setTrace (Object trace ) {
248- this .trace = trace ;
256+ this .trace = trace != null ? trace . toString () : null ;
249257 return this ;
250258 }
251259
@@ -257,7 +265,7 @@ public Builder setSpanId(String spanId) {
257265
258266 /** Sets the ID of the trace span associated with the log entry, if any. */
259267 public Builder setSpanId (Object spanId ) {
260- this .spanId = spanId ;
268+ this .spanId = spanId != null ? spanId . toString () : null ;
261269 return this ;
262270 }
263271
@@ -575,6 +583,142 @@ com.google.logging.v2.LogEntry toPb(String projectId) {
575583 return builder .build ();
576584 }
577585
586+ /**
587+ * Customized serializers to match the expected format for timestamp, source location and request
588+ * method
589+ */
590+ static final class InstantSerializer implements JsonSerializer <Instant > {
591+ @ Override
592+ public JsonElement serialize (
593+ Instant src , java .lang .reflect .Type typeOfSrc , JsonSerializationContext context ) {
594+ return new JsonPrimitive (src .toString ());
595+ }
596+ }
597+
598+ static final class SourceLocationSerializer implements JsonSerializer <SourceLocation > {
599+ @ Override
600+ public JsonElement serialize (
601+ SourceLocation src , java .lang .reflect .Type typeOfSrc , JsonSerializationContext context ) {
602+ JsonObject obj = new JsonObject ();
603+ if (src .getFile () != null ) {
604+ obj .addProperty ("file" , src .getFile ());
605+ }
606+ if (src .getLine () != null ) {
607+ obj .addProperty ("line" , src .getLine ().toString ());
608+ }
609+ if (src .getFunction () != null ) {
610+ obj .addProperty ("function" , src .getFunction ());
611+ }
612+ return obj ;
613+ }
614+ }
615+
616+ static final class RequestMethodSerializer implements JsonSerializer <HttpRequest .RequestMethod > {
617+ @ Override
618+ public JsonElement serialize (
619+ HttpRequest .RequestMethod src ,
620+ java .lang .reflect .Type typeOfSrc ,
621+ JsonSerializationContext context ) {
622+ return new JsonPrimitive (src .name ());
623+ }
624+ }
625+
626+ /** Helper class to format one line Json representation of the LogEntry for structured log. */
627+ static final class StructuredLogFormatter {
628+ private final Gson gson ;
629+ private final StringBuilder builder ;
630+
631+ public StructuredLogFormatter (StringBuilder builder ) {
632+ checkNotNull (builder );
633+ this .gson =
634+ new GsonBuilder ()
635+ .registerTypeAdapter (Instant .class , new InstantSerializer ())
636+ .registerTypeAdapter (SourceLocation .class , new SourceLocationSerializer ())
637+ .registerTypeAdapter (HttpRequest .RequestMethod .class , new RequestMethodSerializer ())
638+ .create ();
639+ this .builder = builder ;
640+ }
641+
642+ /**
643+ * Adds a Json field and value pair to the current string representation. Method does not
644+ * validate parameters to be multi-line strings. Nothing is added if {@code value} parameter is
645+ * {@code null}.
646+ *
647+ * @param name a valid Json field name string.
648+ * @param value an object to be serialized to Json using {@link Gson}.
649+ * @param appendComma a flag to add a trailing comma.
650+ * @return a reference to this object.
651+ */
652+ public StructuredLogFormatter appendField (String name , Object value , boolean appendComma ) {
653+ checkNotNull (name );
654+ if (value != null ) {
655+ builder .append (gson .toJson (name )).append (":" ).append (gson .toJson (value ));
656+ if (appendComma ) {
657+ builder .append ("," );
658+ }
659+ }
660+ return this ;
661+ }
662+
663+ public StructuredLogFormatter appendField (String name , Object value ) {
664+ return appendField (name , value , true );
665+ }
666+
667+ /**
668+ * Serializes a dictionary of key, values as Json fields.
669+ *
670+ * @param value a {@link Map} of key, value arguments to be serialized using {@link Gson}.
671+ * @param appendComma a flag to add a trailing comma.
672+ * @return a reference to this object.
673+ */
674+ public StructuredLogFormatter appendDict (Map <String , Object > value , boolean appendComma ) {
675+ if (value != null ) {
676+ String json = gson .toJson (value );
677+ // append json object without brackets
678+ if (json .length () > 1 ) {
679+ builder .append (json .substring (0 , json .length () - 1 ).substring (1 ));
680+ if (appendComma ) {
681+ builder .append ("," );
682+ }
683+ }
684+ }
685+ return this ;
686+ }
687+ }
688+
689+ /**
690+ * Serializes the object to a one line JSON string in the simplified format that can be parsed by
691+ * the logging agents that run on Google Cloud resources.
692+ */
693+ public String toStructuredJsonString () {
694+ if (payload .getType () == Type .PROTO ) {
695+ throw new UnsupportedOperationException ("LogEntry with protobuf payload cannot be converted" );
696+ }
697+
698+ final StringBuilder builder = new StringBuilder ("{" );
699+ final StructuredLogFormatter formatter = new StructuredLogFormatter (builder );
700+
701+ formatter
702+ .appendField ("severity" , severity )
703+ .appendField ("timestamp" , timestamp )
704+ .appendField ("httpRequest" , httpRequest )
705+ .appendField ("logging.googleapis.com/insertId" , insertId )
706+ .appendField ("logging.googleapis.com/labels" , labels )
707+ .appendField ("logging.googleapis.com/operation" , operation )
708+ .appendField ("logging.googleapis.com/sourceLocation" , sourceLocation )
709+ .appendField ("logging.googleapis.com/spanId" , spanId )
710+ .appendField ("logging.googleapis.com/trace" , trace )
711+ .appendField ("logging.googleapis.com/trace_sampled" , traceSampled );
712+ if (payload .getType () == Type .STRING ) {
713+ formatter .appendField ("message" , payload .getData (), false );
714+ } else if (payload .getType () == Type .JSON ) {
715+ Payload .JsonPayload jsonPayload = (Payload .JsonPayload ) payload ;
716+ formatter .appendDict (jsonPayload .getDataAsMap (), false );
717+ }
718+ builder .append ("}" );
719+ return builder .toString ();
720+ }
721+
578722 /** Returns a builder for {@code LogEntry} objects given the entry payload. */
579723 public static Builder newBuilder (Payload <?> payload ) {
580724 return new Builder (payload );
0 commit comments