//-----------------------------------------------------------------------------
//
// 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 Amazon.Runtime.Internal.Util;
using Amazon.XRay.Recorder.Core.Exceptions;
using Amazon.XRay.Recorder.Core.Internal.Emitters;
using Amazon.XRay.Recorder.Core.Internal.Entities;
using Amazon.XRay.Recorder.Core.Internal.Utils;
using Amazon.XRay.Recorder.Core.Sampling;
using Amazon.XRay.Recorder.Core.Sampling.Local;
using Microsoft.Extensions.Configuration;
namespace Amazon.XRay.Recorder.Core
{
///
/// A collection of methods used to record tracing information for AWS X-Ray.
///
///
public class AWSXRayRecorder : AWSXRayRecorderImpl
{
private static readonly Logger _logger = Logger.GetLogger(typeof(AWSXRayRecorder));
static AWSXRayRecorder _instance = new AWSXRayRecorderBuilder().Build();
public const String LambdaTaskRootKey = "LAMBDA_TASK_ROOT";
public const String LambdaTraceHeaderKey = "_X_AMZN_TRACE_ID";
private static String _lambdaVariables;
private XRayOptions _xRayOptions = new XRayOptions();
///
/// Initializes a new instance of the class.
/// with default configuration.
///
public AWSXRayRecorder() : this(new UdpSegmentEmitter())
{
}
///
/// Initializes a new instance of the class with
///
/// Instance of .
public AWSXRayRecorder(XRayOptions options) : this(new UdpSegmentEmitter(), options)
{
}
///
/// Initializes a new instance of the class
/// with given instance of .
///
/// Instance of .
[CLSCompliant(false)]
public static void InitializeInstance(IConfiguration configuration)
{
XRayOptions xRayOptions = XRayConfiguration.GetXRayOptions(configuration);
var recorderBuilder = GetBuilder(xRayOptions);
Instance = recorderBuilder.Build(xRayOptions);
}
///
/// Initializes provided instance of the class with
/// the instance of .
///
/// Instance of .
/// Instance of .
[CLSCompliant(false)]
public static void InitializeInstance(IConfiguration configuration = null, AWSXRayRecorder recorder = null)
{
XRayOptions xRayOptions = XRayConfiguration.GetXRayOptions(configuration);
var recorderBuilder = GetBuilder(xRayOptions);
if (recorder != null)
{
_logger.DebugFormat("Using custom X-Ray recorder.");
recorder.XRayOptions = xRayOptions;
recorder = recorderBuilder.Build(recorder);
}
else
{
_logger.DebugFormat("Using default X-Ray recorder.");
recorder = recorderBuilder.Build(xRayOptions);
}
Instance = recorder;
}
private static AWSXRayRecorderBuilder GetBuilder(XRayOptions xRayOptions)
{
var recorderBuilder = new AWSXRayRecorderBuilder().WithPluginsFromConfig(xRayOptions).WithContextMissingStrategyFromConfig(xRayOptions);
return recorderBuilder;
}
///
/// Initializes a new instance of the class
/// with given instance of .
///
/// Segment emitter
internal AWSXRayRecorder(ISegmentEmitter emitter) : base(emitter)
{
PopulateContexts();
if (IsLambda())
{
SamplingStrategy = new LocalizedSamplingStrategy();
}
else
{
SamplingStrategy = new DefaultSamplingStrategy();
}
}
///
/// Initializes a new instance of the class
/// with given instance of and instance of .
///
/// Instance of .
/// Instance of .
internal AWSXRayRecorder(ISegmentEmitter emitter, XRayOptions options) : base(emitter)
{
XRayOptions = options;
PopulateContexts();
if (IsLambda())
{
SamplingStrategy = new LocalizedSamplingStrategy(options.SamplingRuleManifest);
}
else
{
SamplingStrategy = new DefaultSamplingStrategy(options.SamplingRuleManifest);
}
}
///
/// Gets the singleton instance of with default configuration.
///
/// An instance of class.
public static AWSXRayRecorder Instance
{
get
{
if (_instance == null)
{
_instance = new AWSXRayRecorderBuilder().Build();
}
return _instance;
}
private set
{
_instance = value;
}
}
///
/// Instance of class.
///
public XRayOptions XRayOptions { get => _xRayOptions; set => _xRayOptions = value; }
///
/// Begin a tracing subsegment. A new segment will be created and added as a subsegment to previous segment/subsegment.
///
/// Name of the operation
/// Sets the start time of the subsegment
/// The argument has a null value.
/// Entity is not available in trace context.
public override void BeginSubsegment(string name, DateTime? timestamp = null)
{
try
{
if (IsTracingDisabled())
{
_logger.DebugFormat("X-Ray tracing is disabled, do not start subsegment");
return;
}
if (IsLambda())
{
ProcessSubsegmentInLambdaContext(name, timestamp);
}
else
{
AddSubsegment(new Subsegment(name), timestamp);
}
}
catch (EntityNotAvailableException e)
{
HandleEntityNotAvailableException(e, "Failed to start subsegment because the parent segment is not available.");
}
}
///
/// Begin a tracing subsegment. A new subsegment will be created and added as a subsegment to previous facade segment or subsegment.
///
private void ProcessSubsegmentInLambdaContext(string name, DateTime? timestamp = null)
{
if (!TraceContext.IsEntityPresent()) // No facade segment available and first subsegment of a subsegment branch needs to be added
{
AddFacadeSegment(name);
AddSubsegmentInLambdaContext(name, timestamp);
}
else // Facade / Subsegment already present
{
var entity = TraceContext.GetEntity(); // can be Facade segment or Subsegment
var environmentRootTraceId = TraceHeader.FromString(AWSXRayRecorder.GetTraceVariablesFromEnvironment()).RootTraceId;
if ((null != environmentRootTraceId) && !environmentRootTraceId.Equals(entity.RootSegment.TraceId)) // If true, customer has leaked subsegments across invocation
{
TraceContext.ClearEntity(); // reset TraceContext
BeginSubsegment(name, timestamp); // This adds Facade segment with updated environment variables
}
else
{
AddSubsegmentInLambdaContext(name, timestamp);
}
}
}
///
/// Begin a Facade Segment.
///
internal void AddFacadeSegment(String name = null)
{
_lambdaVariables = AWSXRayRecorder.GetTraceVariablesFromEnvironment();
_logger.DebugFormat("Lambda Environment detected. Lambda variables: {0}", _lambdaVariables);
if (!TraceHeader.TryParseAll(_lambdaVariables, out TraceHeader traceHeader))
{
if (name != null)
{
_logger.DebugFormat("Lambda variables : {0} for X-Ray trace header environment variable under key : {1} are missing/not valid trace id, parent id or sampling decision, discarding subsegment : {2}", _lambdaVariables, LambdaTraceHeaderKey, name);
}
else
{
_logger.DebugFormat("Lambda variables : {0} for X-Ray trace header environment variable under key : {1} are missing/not valid trace id, parent id or sampling decision, discarding subsegment", _lambdaVariables, LambdaTraceHeaderKey);
}
traceHeader = new TraceHeader();
traceHeader.RootTraceId = TraceId.NewId();
traceHeader.ParentId = null;
traceHeader.Sampled = SampleDecision.NotSampled;
}
Segment newSegment = new FacadeSegment("Facade", traceHeader.RootTraceId, traceHeader.ParentId);
newSegment.Sampled = traceHeader.Sampled;
TraceContext.SetEntity(newSegment);
}
private void AddSubsegmentInLambdaContext(string name, DateTime? timestamp = null)
{
// If the request is not sampled, the passed subsegment will still be available in TraceContext to
// stores the information of the trace. The trace information will still propagated to
// downstream service, in case downstream may overwrite the sample decision.
Entity parentEntity = TraceContext.GetEntity();
Subsegment subsegment = new Subsegment(name);
parentEntity.AddSubsegment(subsegment);
subsegment.Sampled = parentEntity.Sampled;
if (timestamp == null)
{
subsegment.SetStartTimeToNow();
}
else
{
subsegment.SetStartTime(timestamp.Value);
}
TraceContext.SetEntity(subsegment);
}
private void AddSubsegment(Subsegment subsegment, DateTime? timestamp = null)
{
// If the request is not sampled, a segment will still be available in TraceContext to
// stores the information of the trace. The trace information will still propagated to
// downstream service, in case downstream may overwrite the sample decision.
Entity parentEntity = TraceContext.GetEntity();
// If the segment is not sampled, do nothing and exit.
if (parentEntity.Sampled != SampleDecision.Sampled)
{
_logger.DebugFormat("Do not start subsegment because the segment doesn't get sampled. ({0})", subsegment.Name);
return;
}
parentEntity.AddSubsegment(subsegment);
subsegment.Sampled = parentEntity.Sampled;
if (timestamp == null)
{
subsegment.SetStartTimeToNow();
}
else
{
subsegment.SetStartTime(timestamp.Value);
}
TraceContext.SetEntity(subsegment);
}
///
/// End a subsegment.
///
/// Sets the end time for the subsegment
/// Entity is not available in trace context.
public override void EndSubsegment(DateTime? timestamp = null)
{
try
{
if (IsTracingDisabled())
{
_logger.DebugFormat("X-Ray tracing is disabled, do not end subsegment");
return;
}
if (IsLambda())
{
ProcessEndSubsegmentInLambdaContext(timestamp);
}
else
{
ProcessEndSubsegment(timestamp);
}
}
catch (EntityNotAvailableException e)
{
HandleEntityNotAvailableException(e, "Failed to end subsegment because subsegment is not available in trace context.");
}
catch (InvalidCastException e)
{
HandleEntityNotAvailableException(new EntityNotAvailableException("Failed to cast the entity to Subsegment.", e), "Failed to cast the entity to Subsegment.");
}
}
private void ProcessEndSubsegmentInLambdaContext(DateTime? timestamp = null)
{
var subsegment = PrepEndSubsegmentInLambdaContext();
if (timestamp == null)
{
subsegment.SetEndTimeToNow();
}
else
{
subsegment.SetEndTime(timestamp.Value);
}
// Check emittable
if (subsegment.IsEmittable())
{
// Emit
Emitter.Send(subsegment.RootSegment);
}
else if (StreamingStrategy.ShouldStream(subsegment))
{
StreamingStrategy.Stream(subsegment.RootSegment, Emitter);
}
if (TraceContext.IsEntityPresent() && TraceContext.GetEntity().GetType() == typeof(FacadeSegment)) //implies FacadeSegment in the Trace Context
{
EndFacadeSegment();
return;
}
}
private Subsegment PrepEndSubsegmentInLambdaContext()
{
// If the request is not sampled, a subsegment will still be available in TraceContext.
//This behavor is specific to AWS Lambda environment
Entity entity = TraceContext.GetEntity();
Subsegment subsegment = (Subsegment)entity;
subsegment.IsInProgress = false;
// Restore parent segment to trace context
if (subsegment.Parent != null)
{
TraceContext.SetEntity(subsegment.Parent);
}
// Drop ref count
subsegment.Release();
return subsegment;
}
private void EndFacadeSegment()
{
try
{
// If the request is not sampled, a segment will still be available in TraceContext.
// Need to clean up the segment, but do not emit it.
FacadeSegment facadeSegment = (FacadeSegment)TraceContext.GetEntity();
if (!IsTracingDisabled())
{
PrepEndSegment(facadeSegment);
if (facadeSegment.RootSegment != null && facadeSegment.RootSegment.Size >= 0)
{
StreamingStrategy.Stream(facadeSegment, Emitter); //Facade segment is not emitted, all its subsegments, if emmittable, are emitted
}
}
TraceContext.ClearEntity();
}
catch (EntityNotAvailableException e)
{
HandleEntityNotAvailableException(e, "Failed to end facade segment because cannot get the segment from trace context.");
}
catch (InvalidCastException e)
{
HandleEntityNotAvailableException(new EntityNotAvailableException("Failed to cast the entity to Facade segment.", e), "Failed to cast the entity to Facade Segment.");
}
}
///
/// Checks whether current execution is in AWS Lambda.
///
/// Returns true if current execution is in AWS Lambda.
public static Boolean IsLambda()
{
var lambdaTaskRootKey = Environment.GetEnvironmentVariable(LambdaTaskRootKey);
if (!Object.Equals(lambdaTaskRootKey, null))
{
return true;
}
return false;
}
///
/// Returns value set for environment variable "_X_AMZN_TRACE_ID"
///
private static String GetTraceVariablesFromEnvironment()
{
var lambdaTraceHeader = Environment.GetEnvironmentVariable(LambdaTraceHeaderKey);
return lambdaTraceHeader;
}
///
/// Checks whether Tracing is enabled or disabled.
///
/// Returns true if Tracing is disabled else false.
public override bool IsTracingDisabled()
{
return XRayOptions.IsXRayTracingDisabled;
}
///
/// Configures Logger to .
///
/// Enum of .
public static void RegisterLogger(Amazon.LoggingOptions loggingOptions)
{
AWSConfigs.LoggingConfig.LogTo = loggingOptions;
}
}
}