Instrumenting Outsystems platform with AWS X-Ray

Instrumenting Outsystems platform with AWS X-Ray

  

We have a system consisting of Outsystems platform and several other components running in AWS. Recently, we noticed that AWS has released a product called X-Ray that helps debugging and analyzing performance: https://aws.amazon.com/xray/

I tried instrumenting one of our Outsystems eSpaces with X-Ray using an HttpModule:

public class HttpModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += (sender, e) =>
        {
            var traceId = HttpContext.Current.Request.Headers["X-Amzn-Trace-Id"];
AWSXRayRecorder.Instance.BeginSegment("Outsystems", traceId);
        };

        context.EndRequest += (sender, e) =>
        {
            AWSXRayRecorder.Instance.EndSegment();
        };

        context.Error += (sender, e) =>
        {
            AWSXRayRecorder.Instance.AddException(HttpContext.Current.Error);
        };
    }

    public void Dispose()
    {            
    }        
}

However, I got an exception:

Exception information:
    Exception type: SerializationException
    Exception message: Type 'Amazon.XRay.Recorder.Core.Internal.Entities.Entity' in Assembly 'AWSXRayRecorder.Core, Version=1.1.0.0, Culture=neutral, PublicKeyToken=bc6a608d1bdc3681' is not marked as serializable.

Server stack trace:
   at System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType type)
   at System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context)
   at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitMemberInfo()
   at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)
   at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo)
   at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
   at System.Runtime.Remoting.Channels.BinaryClientFormatterSink.SerializeMessage(IMessage msg, ITransportHeaders& headers, Stream& stream)
   at System.Runtime.Remoting.Channels.BinaryClientFormatterSink.SyncProcessMessage(IMessage msg)

Exception rethrown at [0]:
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at OutSystems.HubEdition.ICompiler.UpdateTenantViews(ICompilerContext context, Int32 espaceId, Int32 tenantId)
   at OutSystems.HubEdition.RuntimePlatform.TenantInfo..ctor(Int32 tenantId, AppInfo appInfo)
   at OutSystems.HubEdition.RuntimePlatform.AppInfo.get_Tenant()

Have you tried instrumenting Outsystems applications with X-Ray?

What is the reason the entity used by XRayRecorder needs to be serialized? Do you know a way to work around this?

Br,

Toni


I got this now working with help from AWS. They gave me a new version AWS AWSXRayRecorder.Core with all the classes in the Amazon.XRay.Recorder.Core.Internal.Entities decorated with the Serializable attribute so that it now works together with the Outsystems implementation. With that in place I was then able to instrument code that called other external components during the http request and am now able to utilize AWS X-Ray console to trace execution and pinpoint and analyze problems.

Below you can find the HttpModule code responsible for creating the X-Ray segments:

using System;
using System.Collections.Generic;
using System.Web;
using Amazon.XRay.Recorder.Core;

public class HttpModule : IHttpModule
{
    // NOTE! In order to register this module, the element is needed in web.config
    // For debugging under Visual Studio development server it's needed under system.webServe
    // For running in Outsystems under IIS it's needed under system.web

    //<add name = "XRay" type= "HttpModule, Enoro.XRayHttpModule" />

    // You can configure Outsystems to automatically add the correct element to web.config by using
    // FactoryConfigurations eSpace with the following Shared Configuration setting:

    //<?xml version = "1.0" encoding="UTF-8"?>
    //<xsl:stylesheet version = "1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    //    <xsl:output method = "xml" indent="yes" encoding="UTF-8"/>

    //    <xsl:template match = "@*|node()" >
    //        < xsl:copy>
    //            <xsl:apply-templates select = "@*|node()" />
    //        </ xsl:copy>
    //    </xsl:template>

    //    <xsl:template match = "/configuration/system.web/httpModules" >
    //        < xsl:copy>
    //            <xsl:apply-templates select = "@*|node()" />
    //            < add name="XRay" type="HttpModule, Enoro.XRayHttpModule" />
    //        </xsl:copy>
    //    </xsl:template>

    //</xsl:stylesheet>

    public void Init(HttpApplication context)
    {
        //ReportEventLog("X-Ray initializing module");

        context.BeginRequest += (sender, e) =>
        {
            //ReportEventLog("X-Ray begin request");
            TryBeginSegment();
        };

        context.EndRequest += (sender, e) =>
        {
            //ReportEventLog("X-Ray end request");
            TryEndSegment();
        };

        context.Error += (sender, e) =>
        {
            //ReportEventLog("X-Ray error during request");
            TryAddException();
        };
    }

    public static string CreateTraceId()
    {
        int version = 1;
        int secondsSinceEpoch = (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
        string epochInHex = secondsSinceEpoch.ToString("x");
        string unique48CharHexString = BitConverter.ToString(Guid.NewGuid().ToByteArray()).Replace("-", "").Substring(0, 24).ToLowerInvariant();
        return string.Format("{0}-{1}-{2}", version, epochInHex, unique48CharHexString);
    }

    void TryBeginSegment()
    {
        try
        {
            // Check if ELB has already populated the trace id header. If not, generate it.
            var traceId = HttpContext.Current.Request.Headers["X-Amzn-Trace-Id"];            
            if (traceId == null)
                traceId = CreateTraceId();

            var request = HttpContext.Current.Request;
            AWSXRayRecorder.Instance.BeginSegment(request.ApplicationPath.TrimStart('/') + "@" + request.Url.Host, traceId);
            AWSXRayRecorder.Instance.SetNamespace("remote");
            var httpInfo = new Dictionary<string, object>();
            httpInfo["url"] = request.Url.AbsoluteUri;
            httpInfo["method"] = request.HttpMethod;
            AWSXRayRecorder.Instance.AddHttpInformation("request", httpInfo);
        }
        catch (Exception)
        {}
    }

    void TryEndSegment()
    {
        try
        {
            //var response = (HttpWebResponse)HttpContext.Current.Response;
            var response = HttpContext.Current.Response;
            AWSXRayRecorder.Instance.EndSegment();
            var httpInfo = new Dictionary<string, object>();
            int statusCode = (int)response.StatusCode;
            httpInfo["status"] = statusCode;
            if (statusCode >= 400 && statusCode <= 499)
            {
                AWSXRayRecorder.Instance.MarkError();
                if (statusCode == 429)
                {
                    AWSXRayRecorder.Instance.MarkThrottle();
                }
            }
            else if (statusCode >= 500 && statusCode <= 599)
            {
                AWSXRayRecorder.Instance.MarkFault();
            }
            //httpInfo["content_length"] = response.ContentLength;
            AWSXRayRecorder.Instance.AddHttpInformation("response", httpInfo);
            AWSXRayRecorder.Instance.EndSegment();
        }
        catch (Exception)
        {}
    }

    void TryAddException()
    {
        try
        {              
            AWSXRayRecorder.Instance.AddException(HttpContext.Current.Error);
        }
        catch (Exception)
        {}
    }

    public void Dispose()
    {            
    }        
}


The next step however is to instrument the calls to the underlying database. In our system we are using MySQL.

Can you tell me if Outsystems platform can provide any way to hook a function to execute some code just before an SQL command is executed and just after it has been completed?

Br,

Toni

Hello Toni,

There is no hook to execute code before or after a generic SQL command is executed.

You can analyze database performance using Lifetime Analytics.

Joao