//----------------------------------------------------------------------------- // // Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. // A copy of the License is located at // // http://aws.amazon.com/apache2.0 // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. // //----------------------------------------------------------------------------- using System; using System.Diagnostics; using System.Linq; using System.Net.Http; using System.Reflection; using Amazon.Runtime; using Amazon.XRay.Recorder.Core.Internal.Entities; using ThirdParty.LitJson; namespace Amazon.XRay.Recorder.Core.Internal.Emitters { /// /// Convert a segment into JSON string /// public class JsonSegmentMarshaller : ISegmentMarshaller { private const string ProtocolHeader = "{\"format\":\"json\",\"version\":1}"; private const char ProtocolDelimiter = '\n'; /// /// Initializes a new instance of the class. /// public JsonSegmentMarshaller() { JsonMapper.RegisterExporter(EntityExporter); JsonMapper.RegisterExporter(ExceptionDescriptorExporter); JsonMapper.RegisterExporter(CauseExporter); JsonMapper.RegisterExporter(AnnotationsExporter); JsonMapper.RegisterExporter(HttpMethodExporter); JsonMapper.RegisterExporter(ConstantClassExporter); JsonMapper.RegisterExporter(DelegateExporter); } /// /// Marshall the segment into JSON string /// /// The segment to parse /// The JSON string parsed from given segment public string Marshall(Entity segment) { return ProtocolHeader + ProtocolDelimiter + JsonMapper.ToJson(segment); } private static void EntityExporter(Entity entity, JsonWriter writer) { writer.WriteObjectStart(); WriteEntityFields(entity, writer); var segment = entity as Segment; if (segment != null) { WriteSegmentFields(segment, writer); } var subsegment = entity as Subsegment; if (subsegment != null) { WriteSubsegmentFields(subsegment, writer); } writer.WriteObjectEnd(); } private static void WriteEntityFields(Entity entity, JsonWriter writer) { if (!string.IsNullOrEmpty(entity.TraceId)) { writer.WritePropertyName("trace_id"); writer.Write(entity.TraceId); } writer.WritePropertyName("id"); writer.Write(entity.Id); writer.WritePropertyName("start_time"); writer.Write(entity.StartTime); writer.WritePropertyName("end_time"); writer.Write(entity.EndTime); if (entity.ParentId != null) { writer.WritePropertyName("parent_id"); writer.Write(entity.ParentId); } writer.WritePropertyName("name"); writer.Write(entity.Name); if (entity.IsSubsegmentsAdded) { writer.WritePropertyName("subsegments"); JsonMapper.ToJson(entity.Subsegments, writer); } if (entity.IsAwsAdded) { writer.WritePropertyName("aws"); JsonMapper.ToJson(entity.Aws, writer); } if (entity.HasFault) { writer.WritePropertyName("fault"); writer.Write(entity.HasFault); } if (entity.HasError) { writer.WritePropertyName("error"); writer.Write(entity.HasError); } if (entity.IsThrottled) { writer.WritePropertyName("throttle"); writer.Write(true); } if (entity.Cause != null) { writer.WritePropertyName("cause"); JsonMapper.ToJson(entity.Cause, writer); } if (entity.IsAnnotationsAdded) { writer.WritePropertyName("annotations"); JsonMapper.ToJson(entity.Annotations, writer); } if (entity.IsMetadataAdded) { writer.WritePropertyName("metadata"); JsonMapper.ToJson(entity.Metadata, writer); } if (entity.IsHttpAdded) { writer.WritePropertyName("http"); JsonMapper.ToJson(entity.Http, writer); } if (entity.IsSqlAdded) { writer.WritePropertyName("sql"); JsonMapper.ToJson(entity.Sql, writer); } } private static void WriteSegmentFields(Segment segment, JsonWriter writer) { if (segment.Origin != null) { writer.WritePropertyName("origin"); writer.Write(segment.Origin); } if (segment.IsServiceAdded) { writer.WritePropertyName("service"); JsonMapper.ToJson(segment.Service, writer); } if (segment.User != null) { writer.WritePropertyName("user"); JsonMapper.ToJson(segment.User, writer); } } private static void WriteSubsegmentFields(Subsegment subsegment, JsonWriter writer) { if (subsegment.Type != null) { writer.WritePropertyName("type"); writer.Write(subsegment.Type); } if (subsegment.Namespace != null) { writer.WritePropertyName("namespace"); JsonMapper.ToJson(subsegment.Namespace, writer); } if (subsegment.IsPrecursorIdAdded) { writer.WritePropertyName("precursor_ids"); JsonMapper.ToJson(subsegment.PrecursorIds.ToArray(), writer); } } private static void CauseExporter(Cause cause, JsonWriter writer) { // Propagating faults (e.g. exceptions) can refer to the local root cause exception with its ID rather than duplicating the exceptions. // format -> "cause" : "4fe5fbae3f9e29c1" if (cause.IsExceptionAdded) { var firstException = cause.ExceptionDescriptors[0]; if (firstException.Cause != null && firstException.Id == null) { writer.Write(firstException.Cause); return; } } writer.WriteObjectStart(); if (cause.WorkingDirectory != null) { writer.WritePropertyName("working_directory"); writer.Write(cause.WorkingDirectory); } if (cause.Paths != null) { writer.WritePropertyName("paths"); JsonMapper.ToJson(cause.Paths, writer); } if (cause.IsExceptionAdded) { writer.WritePropertyName("exceptions"); JsonMapper.ToJson(cause.ExceptionDescriptors, writer); } writer.WriteObjectEnd(); } private static void ExceptionDescriptorExporter(ExceptionDescriptor descriptor, JsonWriter writer) { writer.WriteObjectStart(); // exception writer.WritePropertyName("id"); writer.Write(descriptor.Id); writer.WritePropertyName("message"); writer.Write(descriptor.Message); writer.WritePropertyName("type"); writer.Write(descriptor.Type); writer.WritePropertyName("remote"); writer.Write(descriptor.Remote); writer.WritePropertyName("stack"); writer.WriteArrayStart(); // stack StackFrame[] frames = descriptor.Stack; if (frames != null) { foreach (StackFrame frame in frames) { writer.WriteObjectStart(); // trace writer.WritePropertyName("path"); writer.Write(frame.GetFileName()); writer.WritePropertyName("line"); writer.Write(frame.GetFileLineNumber()); writer.WritePropertyName("label"); MethodBase method = frame.GetMethod(); string label = method.Name; if (method.ReflectedType != null) { label = method.ReflectedType.FullName + "." + label; } writer.Write(label); writer.WriteObjectEnd(); // trace } } writer.WriteArrayEnd(); // stack if (descriptor.Truncated > 0) { writer.WritePropertyName("truncated"); writer.Write(descriptor.Truncated); } if (descriptor.Cause != null) { writer.WritePropertyName("cause"); writer.Write(descriptor.Cause); } writer.WriteObjectEnd(); // exception } private static void AnnotationsExporter(Annotations annotations, JsonWriter writer) { writer.WriteObjectStart(); foreach (var annotation in annotations) { writer.WritePropertyName(annotation.Key); JsonMapper.ToJson(annotation.Value, writer); } writer.WriteObjectEnd(); } private static void HttpMethodExporter(HttpMethod method, JsonWriter writer) { writer.Write(method.Method); } private static void ConstantClassExporter(ConstantClass constantClass, JsonWriter writer) { writer.Write(constantClass.Value); } private static void DelegateExporter(Delegate method, JsonWriter writer) { var methodInfo = method.Method; var str = $"{methodInfo.ReflectedType.Name}.{methodInfo.Name}({method})"; writer.Write(str); } } }