diff --git a/AWS.Lambda.Powertools.sln b/AWS.Lambda.Powertools.sln index 448443ec6..493173d9a 100644 --- a/AWS.Lambda.Powertools.sln +++ b/AWS.Lambda.Powertools.sln @@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.PowerTools.Metri EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.PowerTools.Tracing.Tests", "libraries\tests\AWS.Lambda.PowerTools.Tracing.Tests\AWS.Lambda.PowerTools.Tracing.Tests.csproj", "{A040AED5-BBB8-4BFA-B2A5-BBD82817B8A5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.PowerTools.Tests", "libraries\tests\AWS.Lambda.PowerTools.Tests\AWS.Lambda.PowerTools.Tests.csproj", "{4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -162,6 +164,18 @@ Global {A040AED5-BBB8-4BFA-B2A5-BBD82817B8A5}.Release|x86.Build.0 = Release|Any CPU {10E1D31D-AFE9-49EC-9041-920DF96AD74D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {10E1D31D-AFE9-49EC-9041-920DF96AD74D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E}.Debug|x64.ActiveCfg = Debug|Any CPU + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E}.Debug|x64.Build.0 = Debug|Any CPU + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E}.Debug|x86.ActiveCfg = Debug|Any CPU + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E}.Debug|x86.Build.0 = Debug|Any CPU + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E}.Release|Any CPU.Build.0 = Release|Any CPU + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E}.Release|x64.ActiveCfg = Release|Any CPU + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E}.Release|x64.Build.0 = Release|Any CPU + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E}.Release|x86.ActiveCfg = Release|Any CPU + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {58F2F304-9E21-4902-8AD7-66C2022F3524} = {9E22D53B-4F30-4B82-98A2-9242B071D733} @@ -180,5 +194,6 @@ Global {0739912E-E3B6-4513-9E56-128749A78626} = {9E22D53B-4F30-4B82-98A2-9242B071D733} {5B3BBAF7-F8ED-4E2D-B161-0016942542B1} = {0739912E-E3B6-4513-9E56-128749A78626} {10E1D31D-AFE9-49EC-9041-920DF96AD74D} = {5B3BBAF7-F8ED-4E2D-B161-0016942542B1} + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E} = {1CFF5568-8486-475F-81F6-06105C437528} EndGlobalSection EndGlobal diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 000000000..cb4fe3d70 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,454 @@ +# Created by https://www.toptal.com/developers/gitignore/api/dotnetcore,visualstudio,visualstudiocode,rider +# Edit at https://www.toptal.com/developers/gitignore?templates=dotnetcore,visualstudio,visualstudiocode,rider + +### DotnetCore ### +# .NET Core build folders +bin/ +obj/ + +# Common node modules locations +/node_modules +/wwwroot/node_modules + +### Rider ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# End of https://www.toptal.com/developers/gitignore/api/dotnetcore,visualstudio,visualstudiocode,rider +.DS_Store diff --git a/libraries/src/AWS.Lambda.PowerTools.Metrics/Metrics.cs b/libraries/src/AWS.Lambda.PowerTools.Metrics/Metrics.cs index e738739e3..da08b6e08 100644 --- a/libraries/src/AWS.Lambda.PowerTools.Metrics/Metrics.cs +++ b/libraries/src/AWS.Lambda.PowerTools.Metrics/Metrics.cs @@ -8,7 +8,7 @@ public class Metrics : IMetrics private MetricsContext _context; private bool _isColdStart = true; private bool _captureMetricsEvenIfEmpty; - + /// /// Creates Metrics with no namespace or service name defined - requires that they are defined after initialization /// diff --git a/libraries/src/AWS.Lambda.PowerTools.Tracing/AWS.Lambda.PowerTools.Tracing.csproj b/libraries/src/AWS.Lambda.PowerTools.Tracing/AWS.Lambda.PowerTools.Tracing.csproj index 653721759..09cd1528b 100644 --- a/libraries/src/AWS.Lambda.PowerTools.Tracing/AWS.Lambda.PowerTools.Tracing.csproj +++ b/libraries/src/AWS.Lambda.PowerTools.Tracing/AWS.Lambda.PowerTools.Tracing.csproj @@ -2,7 +2,16 @@ netcoreapp3.1 - Amazon.LambdaPowertools.Tracing + Amazon.Lambda.PowerTools.Tracing + + + + + + + + + diff --git a/libraries/src/AWS.Lambda.PowerTools.Tracing/CaptureHandler.cs b/libraries/src/AWS.Lambda.PowerTools.Tracing/CaptureHandler.cs deleted file mode 100644 index edb2fc330..000000000 --- a/libraries/src/AWS.Lambda.PowerTools.Tracing/CaptureHandler.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace Amazon.LambdaPowertools.Tracing -{ - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - public class CaptureHandler : Attribute - { - public CaptureHandler(ITracerOptions options) - { - // not implemented - } - - public CaptureHandler(string service = "undefined", bool disabled = false, bool autoPatch = true, - string patchmodules = null) - { - // not implemented - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools.Tracing/CaptureMethod.cs b/libraries/src/AWS.Lambda.PowerTools.Tracing/CaptureMethod.cs deleted file mode 100644 index 5dce472d7..000000000 --- a/libraries/src/AWS.Lambda.PowerTools.Tracing/CaptureMethod.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Amazon.LambdaPowertools.Tracing -{ - /// - /// Decorator to trace a function execution - /// Creates subsegment named after the method - /// Optionally supports both sync and async method - /// Adds function response as trace metadata using service attribute as metadata namespace - /// - - [AttributeUsage(AttributeTargets.Method, Inherited = false)] - public class CaptureMethod : Attribute - { - public CaptureMethod(ITracerOptions options) - { - // not implemented - } - - public CaptureMethod(string service = "undefined", bool disabled = false, bool autoPatch = true, - string patchmodules = null) - { - // not implemented - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools.Tracing/ITracer.cs b/libraries/src/AWS.Lambda.PowerTools.Tracing/ITracer.cs deleted file mode 100644 index ac0c2e10b..000000000 --- a/libraries/src/AWS.Lambda.PowerTools.Tracing/ITracer.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -namespace Amazon.LambdaPowertools.Tracing -{ - public interface ITracer - { - public void Patch(List modules); - public void PutAnnotation(string key, string value); - public void PutMetadata(string key, string value) ; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools.Tracing/ITracerOptions.cs b/libraries/src/AWS.Lambda.PowerTools.Tracing/ITracerOptions.cs deleted file mode 100644 index a9886f30e..000000000 --- a/libraries/src/AWS.Lambda.PowerTools.Tracing/ITracerOptions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace Amazon.LambdaPowertools.Tracing -{ - public interface ITracerOptions - { - /// - /// Service: default "service_undefined", env:POWERTOOLS_SERVICE_NAME - /// - public string Service { get; set; } - - /// - /// Explicitly disable tracing via env var POWERTOOLS_TRACE_DISABLED="true" - /// - public bool Disabled { get; set; } - - /// - /// If true, it'll use X-Ray to patch all supported libraries at initialization - /// - public bool AutoPatch { get; set; } - - /// - /// Tuple of specific supported modules by X-Ray that should be patched - /// - public Tuple PatchModules { get; set; } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools.Tracing/Internal/IXRayRecorder.cs b/libraries/src/AWS.Lambda.PowerTools.Tracing/Internal/IXRayRecorder.cs new file mode 100644 index 000000000..8ace7eef6 --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools.Tracing/Internal/IXRayRecorder.cs @@ -0,0 +1,13 @@ +using System; + +namespace Amazon.Lambda.PowerTools.Tracing.Internal +{ + public interface IXRayRecorder + { + void BeginSubsegment(string name, DateTime? timestamp = null); + void SetNamespace(string value); + void AddAnnotation(string key, object value); + void AddMetadata(string nameSpace, string key, object value); + void EndSubsegment(); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools.Tracing/Internal/TracingAspectHandler.cs b/libraries/src/AWS.Lambda.PowerTools.Tracing/Internal/TracingAspectHandler.cs new file mode 100644 index 000000000..e5cd3f935 --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools.Tracing/Internal/TracingAspectHandler.cs @@ -0,0 +1,134 @@ +using System; +using AWS.Lambda.PowerTools.Aspects; +using AWS.Lambda.PowerTools.Core; + +namespace Amazon.Lambda.PowerTools.Tracing.Internal +{ + internal class TracingAspectHandler : IMethodAspectAttribute + { + private readonly string _segmentName; + private readonly string _namespace; + private readonly TracingCaptureMode _captureMode; + private readonly IPowerToolsConfigurations _powerToolsConfigurations; + private readonly IXRayRecorder _xRayRecorder; + + private static bool _isColdStart = true; + private static bool _captureAnnotations = true; + private bool _isAnnotationsCaptured; + + internal TracingAspectHandler + ( + string segmentName, + string nameSpace, + TracingCaptureMode captureMode, + IPowerToolsConfigurations powerToolsConfigurations, + IXRayRecorder xRayRecorder + ) + { + _segmentName = segmentName; + _namespace = nameSpace; + _captureMode = captureMode; + _powerToolsConfigurations = powerToolsConfigurations; + _xRayRecorder = xRayRecorder; + } + + private string GetNamespace() + { + return !string.IsNullOrWhiteSpace(_namespace) ? _namespace : _powerToolsConfigurations.ServiceName; + } + + private bool CaptureResponse() + { + switch (_captureMode) + { + case TracingCaptureMode.EnvironmentVariable: + return _powerToolsConfigurations.TracerCaptureResponse; + case TracingCaptureMode.Response: + case TracingCaptureMode.ResponseAndError: + return true; + case TracingCaptureMode.Error: + case TracingCaptureMode.Disabled: + default: + return false; + } + } + + private bool CaptureError() + { + switch (_captureMode) + { + case TracingCaptureMode.EnvironmentVariable: + return _powerToolsConfigurations.TracerCaptureError; + case TracingCaptureMode.Error: + case TracingCaptureMode.ResponseAndError: + return true; + case TracingCaptureMode.Response: + case TracingCaptureMode.Disabled: + default: + return false; + } + } + + public void OnEntry(AspectEventArgs eventArgs) + { + var segmentName = !string.IsNullOrWhiteSpace(_segmentName) ? _segmentName : $"## {eventArgs.Name}"; + var nameSpace = GetNamespace(); + + _xRayRecorder.BeginSubsegment(segmentName); + _xRayRecorder.SetNamespace(nameSpace); + + if (_captureAnnotations) + { + _xRayRecorder.AddAnnotation("ColdStart", _isColdStart); + + _isColdStart = false; + _captureAnnotations = false; + _isAnnotationsCaptured = true; + + if (_powerToolsConfigurations.IsServiceNameDefined) + _xRayRecorder.AddAnnotation("Service", _powerToolsConfigurations.ServiceName); + } + } + + public void OnSuccess(AspectEventArgs eventArgs, object result) + { + if (CaptureResponse()) + { + var nameSpace = GetNamespace(); + + _xRayRecorder.AddMetadata + ( + nameSpace: nameSpace, + key: $"{eventArgs.Name} response", + value: result + ); + } + } + + public T OnException(AspectEventArgs eventArgs, Exception exception) + { + if (CaptureError()) + { + var nameSpace = GetNamespace(); + + _xRayRecorder.AddMetadata + ( + nameSpace: nameSpace, + key: $"{eventArgs.Name} error", + value: exception + ); + } + + throw exception; + } + + public void OnExit(AspectEventArgs eventArgs) + { + if (_isAnnotationsCaptured) + _captureAnnotations = true; + + if (!_powerToolsConfigurations.IsSamLocal) + _xRayRecorder.EndSubsegment(); + } + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools.Tracing/Internal/XRayRecorder.cs b/libraries/src/AWS.Lambda.PowerTools.Tracing/Internal/XRayRecorder.cs new file mode 100644 index 000000000..8778c043b --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools.Tracing/Internal/XRayRecorder.cs @@ -0,0 +1,36 @@ +using System; +using Amazon.XRay.Recorder.Core; + +namespace Amazon.Lambda.PowerTools.Tracing.Internal +{ + internal class XRayRecorder : IXRayRecorder + { + private static IXRayRecorder _instance; + public static IXRayRecorder Instance => _instance ??= new XRayRecorder(); + + public void BeginSubsegment(string name, DateTime? timestamp = null) + { + AWSXRayRecorder.Instance.BeginSubsegment(name, timestamp); + } + + public void SetNamespace(string value) + { + AWSXRayRecorder.Instance.SetNamespace(value); + } + + public void AddAnnotation(string key, object value) + { + AWSXRayRecorder.Instance.AddAnnotation(key, value); + } + + public void AddMetadata(string nameSpace, string key, object value) + { + AWSXRayRecorder.Instance.AddMetadata(nameSpace, key, value); + } + + public void EndSubsegment() + { + AWSXRayRecorder.Instance.EndSubsegment(); + } + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools.Tracing/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.PowerTools.Tracing/InternalsVisibleTo.cs new file mode 100644 index 000000000..04e7e0737 --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools.Tracing/InternalsVisibleTo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("AWS.Lambda.PowerTools.Tracing.Tests")] \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools.Tracing/Tracer.cs b/libraries/src/AWS.Lambda.PowerTools.Tracing/Tracer.cs deleted file mode 100644 index 9daa356c7..000000000 --- a/libraries/src/AWS.Lambda.PowerTools.Tracing/Tracer.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; - -namespace Amazon.LambdaPowertools.Tracing -{ - public class Tracer : ITracer - { - public void Patch(List modules) - { - throw new System.NotImplementedException(); - } - - public void PutAnnotation(string key, string value) - { - throw new System.NotImplementedException(); - } - - public void PutMetadata(string key, string value) - { - throw new System.NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools.Tracing/TracingAttribute.cs b/libraries/src/AWS.Lambda.PowerTools.Tracing/TracingAttribute.cs new file mode 100644 index 000000000..f8cb40d69 --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools.Tracing/TracingAttribute.cs @@ -0,0 +1,45 @@ +using System; +using Amazon.Lambda.PowerTools.Tracing.Internal; +using AWS.Lambda.PowerTools.Aspects; +using AWS.Lambda.PowerTools.Core; + +namespace Amazon.Lambda.PowerTools.Tracing +{ + public class TracingAttribute : MethodAspectAttribute + { + public string SegmentName { get; set; } = ""; + public string Namespace { get; set; } = ""; + public TracingCaptureMode CaptureMode { get; set; } = TracingCaptureMode.EnvironmentVariable; + + private IMethodAspectAttribute _tracingHandler; + private IMethodAspectAttribute TracingHandler => + _tracingHandler ??= new TracingAspectHandler + ( + SegmentName, + Namespace, + CaptureMode, + PowerToolsConfigurations.Instance, + XRayRecorder.Instance + ); + + public override void OnEntry(AspectEventArgs eventArgs) + { + TracingHandler.OnEntry(eventArgs); + } + + public override void OnSuccess(AspectEventArgs eventArgs, object result) + { + TracingHandler.OnSuccess(eventArgs, result); + } + + public override T OnException(AspectEventArgs eventArgs, Exception exception) + { + return TracingHandler.OnException(eventArgs, exception); + } + + public override void OnExit(AspectEventArgs eventArgs) + { + TracingHandler.OnExit(eventArgs); + } + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools.Tracing/TracingCaptureMode.cs b/libraries/src/AWS.Lambda.PowerTools.Tracing/TracingCaptureMode.cs new file mode 100644 index 000000000..2649898f5 --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools.Tracing/TracingCaptureMode.cs @@ -0,0 +1,37 @@ +namespace Amazon.Lambda.PowerTools.Tracing +{ + public enum TracingCaptureMode + { + /// + /// Enables attribute to capture only response. If this mode is explicitly overridden + /// on { attribute, it will override value of environment variable POWERTOOLS_TRACER_CAPTURE_RESPONSE + /// + Response, + + /// + /// Enabled attribute to capture only error from the method. If this mode is explicitly overridden + /// on attribute, it will override value of environment variable POWERTOOLS_TRACER_CAPTURE_ERROR + /// + Error, + + /// + /// Enabled attribute to capture both response error from the method. If this mode is explicitly overridden + /// on attribute, it will override value of environment variables POWERTOOLS_TRACER_CAPTURE_RESPONSE + /// and POWERTOOLS_TRACER_CAPTURE_ERROR + /// + ResponseAndError, + + /// + /// Disables attribute to capture both response and error from the method. If this mode is explicitly overridden + /// on attribute, it will override values of environment variable POWERTOOLS_TRACER_CAPTURE_RESPONSE + /// and POWERTOOLS_TRACER_CAPTURE_ERROR + /// + Disabled, + + /// + /// Enables/Disables attribute to capture response and error from the method based on the value of + /// environment variable POWERTOOLS_TRACER_CAPTURE_RESPONSE and POWERTOOLS_TRACER_CAPTURE_ERROR + /// + EnvironmentVariable + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools/AWS.Lambda.PowerTools.csproj b/libraries/src/AWS.Lambda.PowerTools/AWS.Lambda.PowerTools.csproj index 3e2c1189f..d58610d3a 100644 --- a/libraries/src/AWS.Lambda.PowerTools/AWS.Lambda.PowerTools.csproj +++ b/libraries/src/AWS.Lambda.PowerTools/AWS.Lambda.PowerTools.csproj @@ -10,4 +10,8 @@ latest + + + + diff --git a/libraries/src/AWS.Lambda.PowerTools/Aspects/AspectEventArgs.cs b/libraries/src/AWS.Lambda.PowerTools/Aspects/AspectEventArgs.cs new file mode 100644 index 000000000..3f627a6fa --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools/Aspects/AspectEventArgs.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace AWS.Lambda.PowerTools.Aspects +{ + public class AspectEventArgs : EventArgs + { + public object Instance { get; internal set; } + public Type Type { get; internal set; } + public MethodBase Method { get; internal set; } + public string Name { get; internal set; } + public IReadOnlyList Args { get; internal set; } + public Type ReturnType { get; internal set; } + public Attribute[] Triggers { get; internal set; } + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools/Aspects/IMethodAspectAttribute.cs b/libraries/src/AWS.Lambda.PowerTools/Aspects/IMethodAspectAttribute.cs new file mode 100644 index 000000000..40f5d3202 --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools/Aspects/IMethodAspectAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace AWS.Lambda.PowerTools.Aspects +{ + public interface IMethodAspectAttribute + { + void OnEntry(AspectEventArgs eventArgs); + void OnSuccess(AspectEventArgs eventArgs, object result); + T OnException(AspectEventArgs eventArgs, Exception exception); + void OnExit(AspectEventArgs eventArgs); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools/Aspects/MethodAspectAttribute.cs b/libraries/src/AWS.Lambda.PowerTools/Aspects/MethodAspectAttribute.cs new file mode 100644 index 000000000..66a061a2a --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools/Aspects/MethodAspectAttribute.cs @@ -0,0 +1,66 @@ +using System; +using System.Threading.Tasks; +using AspectInjector.Broker; + +namespace AWS.Lambda.PowerTools.Aspects +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] + [Injection(typeof(UniversalWrapperAspect), Inherited = true)] + public abstract class MethodAspectAttribute : UniversalWrapperAttribute, IMethodAspectAttribute + { + protected internal sealed override T WrapSync(Func target, object[] args, AspectEventArgs eventArgs) + { + OnEntry(eventArgs); + try + { + var result = base.WrapSync(target, args, eventArgs); + OnSuccess(eventArgs, result); + return result; + } + catch (Exception exception) + { + return OnException(eventArgs, exception); + } + finally + { + OnExit(eventArgs); + } + } + + protected internal sealed override async Task WrapAsync(Func> target, object[] args, AspectEventArgs eventArgs) + { + OnEntry(eventArgs); + try + { + var result = await base.WrapSync(target, args, eventArgs); + OnSuccess(eventArgs, result); + return result; + } + catch (Exception exception) + { + return OnException(eventArgs, exception); + } + finally + { + OnExit(eventArgs); + } + } + + public virtual void OnEntry(AspectEventArgs eventArgs) + { + } + + public virtual void OnSuccess(AspectEventArgs eventArgs, object result) + { + } + + public virtual T OnException(AspectEventArgs eventArgs, Exception exception) + { + throw exception; + } + + public virtual void OnExit(AspectEventArgs eventArgs) + { + } + } +} diff --git a/libraries/src/AWS.Lambda.PowerTools/Aspects/UniversalWrapperAspect.cs b/libraries/src/AWS.Lambda.PowerTools/Aspects/UniversalWrapperAspect.cs new file mode 100644 index 000000000..958a4099c --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools/Aspects/UniversalWrapperAspect.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using AspectInjector.Broker; + +namespace AWS.Lambda.PowerTools.Aspects +{ + [Aspect(Scope.Global)] + public class UniversalWrapperAspect + { + [Advice(Kind.Around, Targets = Target.Method)] + public object Handle( + [Argument(Source.Instance)] object instance, + [Argument(Source.Type)] Type type, + [Argument(Source.Metadata)] MethodBase method, + [Argument(Source.Target)] Func target, + [Argument(Source.Name)] string name, + [Argument(Source.Arguments)] object[] args, + [Argument(Source.ReturnType)] Type returnType, + [Argument(Source.Triggers)] Attribute[] triggers) + { + var eventArgs = new AspectEventArgs + { + Instance = instance, + Type = type, + Method = method, + Name = name, + Args = args, + ReturnType = returnType, + Triggers = triggers + }; + + var wrappers = triggers.OfType().ToArray(); + var handler = GetMethodHandler(method, returnType, wrappers); + return handler(target, args, eventArgs); + } + + private delegate object Handler(Func next, object[] args, AspectEventArgs eventArgs); + + private static readonly Dictionary _delegateCache = new Dictionary(); + + private static readonly MethodInfo _asyncGenericHandler = + typeof(UniversalWrapperAttribute).GetMethod(nameof(UniversalWrapperAttribute.WrapAsync), BindingFlags.NonPublic | BindingFlags.Instance); + + private static readonly MethodInfo _syncGenericHandler = + typeof(UniversalWrapperAttribute).GetMethod(nameof(UniversalWrapperAttribute.WrapSync), BindingFlags.NonPublic | BindingFlags.Instance); + + private static Handler CreateMethodHandler(Type returnType, IEnumerable wrappers) + { + var targetParam = Expression.Parameter(typeof(Func), "orig"); + var eventArgsParam = Expression.Parameter(typeof(AspectEventArgs), "event"); + + MethodInfo wrapperMethod; + + if (typeof(Task).IsAssignableFrom(returnType)) + { + var taskType = returnType.IsConstructedGenericType ? returnType.GenericTypeArguments[0] : Type.GetType("System.Threading.Tasks.VoidTaskResult"); + returnType = typeof(Task<>).MakeGenericType(new[] { taskType }); + wrapperMethod = _asyncGenericHandler.MakeGenericMethod(new[] { taskType }); + } + else + { + if (returnType == typeof(void)) + returnType = typeof(object); + wrapperMethod = _syncGenericHandler.MakeGenericMethod(new[] { returnType }); + } + + var converArgs = Expression.Parameter(typeof(object[]), "args"); + var next = Expression.Lambda(Expression.Convert(Expression.Invoke(targetParam, converArgs), returnType), converArgs); + + foreach (var wrapper in wrappers) + { + var argsParam = Expression.Parameter(typeof(object[]), "args"); + next = Expression.Lambda(Expression.Call(Expression.Constant(wrapper), wrapperMethod, next, argsParam, eventArgsParam), argsParam); + } + + var orig_args = Expression.Parameter(typeof(object[]), "orig_args"); + var handler = Expression.Lambda(Expression.Convert(Expression.Invoke(next, orig_args), typeof(object)), targetParam, orig_args, eventArgsParam); + + var handlerCompiled = handler.Compile(); + + return handlerCompiled; + } + + private static Handler GetMethodHandler(MethodBase method, Type returnType, IEnumerable wrappers) + { + if (!_delegateCache.TryGetValue(method, out var handler)) + { + lock (method) + { + if (!_delegateCache.TryGetValue(method, out handler)) + { + _delegateCache[method] = handler = CreateMethodHandler(returnType, wrappers); + } + } + } + return handler; + } + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools/Aspects/UniversalWrapperAttribute.cs b/libraries/src/AWS.Lambda.PowerTools/Aspects/UniversalWrapperAttribute.cs new file mode 100644 index 000000000..d61165aca --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools/Aspects/UniversalWrapperAttribute.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; + +namespace AWS.Lambda.PowerTools.Aspects +{ + public abstract class UniversalWrapperAttribute : Attribute + { + protected internal virtual T WrapSync(Func target, object[] args, AspectEventArgs eventArgs) + { + return target(args); + } + protected internal virtual Task WrapAsync(Func> target, object[] args, AspectEventArgs eventArgs) + { + return target(args); + } + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools/Core/Constants.cs b/libraries/src/AWS.Lambda.PowerTools/Core/Constants.cs new file mode 100644 index 000000000..bba6ff69f --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools/Core/Constants.cs @@ -0,0 +1,10 @@ +namespace AWS.Lambda.PowerTools.Core +{ + internal static class Constants + { + internal const string SERVICE_NAME_ENV = "POWERTOOLS_SERVICE_NAME"; + internal const string SAM_LOCAL_ENV = "AWS_SAM_LOCAL"; + internal const string TRACER_CAPTURE_RESPONSE_ENV = "POWERTOOLS_TRACER_CAPTURE_RESPONSE"; + internal const string TRACER_CAPTURE_ERROR_ENV = "POWERTOOLS_TRACER_CAPTURE_ERROR"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools/Core/IPowerToolsConfigurations.cs b/libraries/src/AWS.Lambda.PowerTools/Core/IPowerToolsConfigurations.cs new file mode 100644 index 000000000..0bbde90db --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools/Core/IPowerToolsConfigurations.cs @@ -0,0 +1,15 @@ +namespace AWS.Lambda.PowerTools.Core +{ + public interface IPowerToolsConfigurations + { + string ServiceName { get; } + bool IsServiceNameDefined { get; } + bool TracerCaptureResponse{ get; } + bool TracerCaptureError{ get; } + bool IsSamLocal{ get; } + + string GetEnvironmentVariable(string variable); + string GetEnvironmentVariableOrDefault(string variable, string defaultValue); + bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools/Core/ISystemWrapper.cs b/libraries/src/AWS.Lambda.PowerTools/Core/ISystemWrapper.cs new file mode 100644 index 000000000..2a2ca2d50 --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools/Core/ISystemWrapper.cs @@ -0,0 +1,7 @@ +namespace AWS.Lambda.PowerTools.Core +{ + public interface ISystemWrapper + { + string GetEnvironmentVariable(string variable); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools/Core/PowerToolsConfigurations.cs b/libraries/src/AWS.Lambda.PowerTools/Core/PowerToolsConfigurations.cs new file mode 100644 index 000000000..3f6948b4d --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools/Core/PowerToolsConfigurations.cs @@ -0,0 +1,48 @@ +namespace AWS.Lambda.PowerTools.Core +{ + public class PowerToolsConfigurations : IPowerToolsConfigurations + { + private static IPowerToolsConfigurations _instance; + public static IPowerToolsConfigurations Instance => _instance ??= new PowerToolsConfigurations(SystemWrapper.Instance); + + private readonly ISystemWrapper _systemWrapper; + + internal PowerToolsConfigurations(ISystemWrapper systemWrapper) + { + _systemWrapper = systemWrapper; + } + + public string GetEnvironmentVariable(string variable) + { + return _systemWrapper.GetEnvironmentVariable(variable); + } + + public string GetEnvironmentVariableOrDefault(string variable, string defaultValue) + { + var result = _systemWrapper.GetEnvironmentVariable(variable); + return string.IsNullOrWhiteSpace(result) ? defaultValue : result; + } + + public bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue) + { + return bool.TryParse(_systemWrapper.GetEnvironmentVariable(variable), out var result) + ? result + : defaultValue; + } + + public string ServiceName => + GetEnvironmentVariableOrDefault(Constants.SERVICE_NAME_ENV, "service_undefined"); + + public bool IsServiceNameDefined => + !string.IsNullOrWhiteSpace(GetEnvironmentVariable(Constants.SERVICE_NAME_ENV)); + + public bool TracerCaptureResponse => + GetEnvironmentVariableOrDefault(Constants.TRACER_CAPTURE_RESPONSE_ENV, true); + + public bool TracerCaptureError => + GetEnvironmentVariableOrDefault(Constants.TRACER_CAPTURE_ERROR_ENV, true); + + public bool IsSamLocal => + GetEnvironmentVariableOrDefault(Constants.SAM_LOCAL_ENV, false); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools/Core/SystemWrapper.cs b/libraries/src/AWS.Lambda.PowerTools/Core/SystemWrapper.cs new file mode 100644 index 000000000..b68cd0980 --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools/Core/SystemWrapper.cs @@ -0,0 +1,17 @@ +using System; + +namespace AWS.Lambda.PowerTools.Core +{ + public class SystemWrapper : ISystemWrapper + { + private static ISystemWrapper _instance; + public static ISystemWrapper Instance => _instance ??= new SystemWrapper(); + + private SystemWrapper() { } + + public string GetEnvironmentVariable(string variable) + { + return Environment.GetEnvironmentVariable(variable); + } + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.PowerTools/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.PowerTools/InternalsVisibleTo.cs new file mode 100644 index 000000000..b7515cdb4 --- /dev/null +++ b/libraries/src/AWS.Lambda.PowerTools/InternalsVisibleTo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("AWS.Lambda.PowerTools.Tests")] +[assembly: InternalsVisibleTo("AWS.Lambda.PowerTools.Tracing.Tests")] \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.PowerTools.Tests/AWS.Lambda.PowerTools.Tests.csproj b/libraries/tests/AWS.Lambda.PowerTools.Tests/AWS.Lambda.PowerTools.Tests.csproj new file mode 100644 index 000000000..c5ab48dd9 --- /dev/null +++ b/libraries/tests/AWS.Lambda.PowerTools.Tests/AWS.Lambda.PowerTools.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/libraries/tests/AWS.Lambda.PowerTools.Tests/Core/PowerToolsConfigurationsTest.cs b/libraries/tests/AWS.Lambda.PowerTools.Tests/Core/PowerToolsConfigurationsTest.cs new file mode 100644 index 000000000..0a4fda649 --- /dev/null +++ b/libraries/tests/AWS.Lambda.PowerTools.Tests/Core/PowerToolsConfigurationsTest.cs @@ -0,0 +1,502 @@ +using System; +using AWS.Lambda.PowerTools.Core; +using Moq; +using Xunit; + +namespace AWS.Lambda.PowerTools.Tests.Core +{ + public class PowerToolsConfigurationsTest + { + #region GetEnvironmentVariable Tests + + [Fact] + public void GetEnvironmentVariableOrDefault_WhenEnvironmentVariableIsNull_ReturnsDefaultValueString() + { + // Arrange + var key = Guid.NewGuid().ToString(); + var defaultValue = Guid.NewGuid().ToString(); + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(key) + ).Returns(string.Empty); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.GetEnvironmentVariableOrDefault(key, defaultValue); + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == key) + ), Times.Once); + + Assert.Equal(result, defaultValue); + } + + [Fact] + public void GetEnvironmentVariableOrDefault_WhenEnvironmentVariableIsNull_ReturnsDefaultValueFalse() + { + // Arrange + var key = Guid.NewGuid().ToString(); + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(key) + ).Returns(string.Empty); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.GetEnvironmentVariableOrDefault(key, false); + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == key) + ), Times.Once); + + Assert.False(result); + } + + [Fact] + public void GetEnvironmentVariableOrDefault_WhenEnvironmentVariableIsNull_ReturnsDefaultValueTrue() + { + // Arrange + var key = Guid.NewGuid().ToString(); + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(key) + ).Returns(string.Empty); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.GetEnvironmentVariableOrDefault(key, true); + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == key) + ), Times.Once); + + Assert.True(result); + } + + [Fact] + public void GetEnvironmentVariableOrDefault_WhenEnvironmentVariableHasValue_ReturnsValueString() + { + // Arrange + var key = Guid.NewGuid().ToString(); + var defaultValue = Guid.NewGuid().ToString(); + var value = Guid.NewGuid().ToString(); + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(key) + ).Returns(value); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.GetEnvironmentVariableOrDefault(key, defaultValue); + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == key) + ), Times.Once); + + Assert.Equal(result, value); + } + + [Fact] + public void GetEnvironmentVariableOrDefault_WhenEnvironmentVariableHasValue_ReturnsValueTrue() + { + // Arrange + var key = Guid.NewGuid().ToString(); + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(key) + ).Returns("true"); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.GetEnvironmentVariableOrDefault(key, false); + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == key) + ), Times.Once); + + Assert.True(result); + } + + [Fact] + public void GetEnvironmentVariableOrDefault_WhenEnvironmentVariableHasValue_ReturnsValueFalse() + { + // Arrange + var key = Guid.NewGuid().ToString(); + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(key) + ).Returns("false"); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.GetEnvironmentVariableOrDefault(key, true); + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == key) + ), Times.Once); + + Assert.False(result); + } + + #endregion + + #region ServiceName Tests + + [Fact] + public void ServiceName_WhenEnvironmentIsNull_ReturnsDefaultValue() + { + // Arrange + var defaultServiceName = "service_undefined"; + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(Constants.SERVICE_NAME_ENV) + ).Returns(string.Empty); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.ServiceName; + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == Constants.SERVICE_NAME_ENV) + ), Times.Once); + + Assert.Equal(result, defaultServiceName); + } + + [Fact] + public void ServiceName_WhenEnvironmentHasValue_ReturnsValue() + { + // Arrange + var serviceName = Guid.NewGuid().ToString(); + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(Constants.SERVICE_NAME_ENV) + ).Returns(serviceName); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.ServiceName; + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == Constants.SERVICE_NAME_ENV) + ), Times.Once); + + Assert.Equal(result, serviceName); + } + + #endregion + + #region IsServiceNameDefined Tests + + [Fact] + public void IsServiceNameDefined_WhenEnvironmentHasValue_ReturnsTrue() + { + // Arrange + var serviceName = Guid.NewGuid().ToString(); + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(Constants.SERVICE_NAME_ENV) + ).Returns(serviceName); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.IsServiceNameDefined; + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == Constants.SERVICE_NAME_ENV) + ), Times.Once); + + Assert.True(result); + } + + [Fact] + public void IsServiceNameDefined_WhenEnvironmentDoesNotHaveValue_ReturnsFalse() + { + // Arrange + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(Constants.SERVICE_NAME_ENV) + ).Returns(string.Empty); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.IsServiceNameDefined; + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == Constants.SERVICE_NAME_ENV) + ), Times.Once); + + Assert.False(result); + } + + #endregion + + #region TracerCaptureResponse Tests + + [Fact] + public void TracerCaptureResponse_WhenEnvironmentIsNull_ReturnsDefaultValue() + { + // Arrange + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(Constants.TRACER_CAPTURE_RESPONSE_ENV) + ).Returns(string.Empty); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.TracerCaptureResponse; + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == Constants.TRACER_CAPTURE_RESPONSE_ENV) + ), Times.Once); + + Assert.True(result); + } + + [Fact] + public void TracerCaptureResponse_WhenEnvironmentHasValue_ReturnsValueFalse() + { + // Arrange + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(Constants.TRACER_CAPTURE_RESPONSE_ENV) + ).Returns("false"); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.TracerCaptureResponse; + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == Constants.TRACER_CAPTURE_RESPONSE_ENV) + ), Times.Once); + + Assert.False(result); + } + + [Fact] + public void TracerCaptureResponse_WhenEnvironmentHasValue_ReturnsValueTrue() + { + // Arrange + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(Constants.TRACER_CAPTURE_RESPONSE_ENV) + ).Returns("true"); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.TracerCaptureResponse; + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == Constants.TRACER_CAPTURE_RESPONSE_ENV) + ), Times.Once); + + Assert.True(result); + } + + #endregion + + #region TracerCaptureError Tests + + [Fact] + public void TracerCaptureError_WhenEnvironmentIsNull_ReturnsDefaultValue() + { + // Arrange + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(Constants.TRACER_CAPTURE_ERROR_ENV) + ).Returns(string.Empty); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.TracerCaptureError; + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == Constants.TRACER_CAPTURE_ERROR_ENV) + ), Times.Once); + + Assert.True(result); + } + + [Fact] + public void TracerCaptureError_WhenEnvironmentHasValue_ReturnsValueFalse() + { + // Arrange + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(Constants.TRACER_CAPTURE_ERROR_ENV) + ).Returns("false"); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.TracerCaptureError; + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == Constants.TRACER_CAPTURE_ERROR_ENV) + ), Times.Once); + + Assert.False(result); + } + + [Fact] + public void TracerCaptureError_WhenEnvironmentHasValue_ReturnsValueTrue() + { + // Arrange + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(Constants.TRACER_CAPTURE_ERROR_ENV) + ).Returns("true"); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.TracerCaptureError; + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == Constants.TRACER_CAPTURE_ERROR_ENV) + ), Times.Once); + + Assert.True(result); + } + + #endregion + + #region TracerCaptureError Tests + + [Fact] + public void IsSamLocal_WhenEnvironmentIsNull_ReturnsDefaultValue() + { + // Arrange + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(Constants.SAM_LOCAL_ENV) + ).Returns(string.Empty); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.IsSamLocal; + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == Constants.SAM_LOCAL_ENV) + ), Times.Once); + + Assert.False(result); + } + + [Fact] + public void IsSamLocal_WhenEnvironmentHasValue_ReturnsValueFalse() + { + // Arrange + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(Constants.SAM_LOCAL_ENV) + ).Returns("false"); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.IsSamLocal; + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == Constants.SAM_LOCAL_ENV) + ), Times.Once); + + Assert.False(result); + } + + [Fact] + public void IsSamLocal_WhenEnvironmentHasValue_ReturnsValueTrue() + { + // Arrange + var systemWrapper = new Mock(); + + systemWrapper.Setup(c => + c.GetEnvironmentVariable(Constants.SAM_LOCAL_ENV) + ).Returns("true"); + + var configurations = new PowerToolsConfigurations(systemWrapper.Object); + + // Act + var result = configurations.IsSamLocal; + + // Assert + systemWrapper.Verify(v => + v.GetEnvironmentVariable( + It.Is(i => i == Constants.SAM_LOCAL_ENV) + ), Times.Once); + + Assert.True(result); + } + + #endregion + } +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.PowerTools.Tracing.Tests/AWS.Lambda.PowerTools.Tracing.Tests.csproj b/libraries/tests/AWS.Lambda.PowerTools.Tracing.Tests/AWS.Lambda.PowerTools.Tracing.Tests.csproj index a2c691f33..ad4ca18e6 100644 --- a/libraries/tests/AWS.Lambda.PowerTools.Tracing.Tests/AWS.Lambda.PowerTools.Tracing.Tests.csproj +++ b/libraries/tests/AWS.Lambda.PowerTools.Tracing.Tests/AWS.Lambda.PowerTools.Tracing.Tests.csproj @@ -8,4 +8,14 @@ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/libraries/tests/AWS.Lambda.PowerTools.Tracing.Tests/TestFunction.cs b/libraries/tests/AWS.Lambda.PowerTools.Tracing.Tests/TestFunction.cs deleted file mode 100644 index 6c1042df3..000000000 --- a/libraries/tests/AWS.Lambda.PowerTools.Tracing.Tests/TestFunction.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading.Tasks; -using Amazon.LambdaPowertools.Tracing; - -namespace AWS.Lambda.PowerTools.Tracing.Tests -{ - - public class TestFunction - { - private readonly ITracer _tracer; - - public TestFunction() - { - _tracer = new Tracer(); - } - - [CaptureMethod(service: "MyService", disabled:false, autoPatch: false, patchmodules: null)] - private string ConfirmBooking(string bookingId) - { - var response = AddConfirmation(bookingId); - - _tracer.PutMetadata("BookingConfirmation", response["requestId"]); - _tracer.PutMetadata("Booking confirmation", "{}"); - _tracer.PutMetadata("", ""); - - - return response.ToString(); - } - - private Dictionary AddConfirmation(string bookingId) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.PowerTools.Tracing.Tests/TracingAttributeTest.cs b/libraries/tests/AWS.Lambda.PowerTools.Tracing.Tests/TracingAttributeTest.cs new file mode 100644 index 000000000..1e95152f9 --- /dev/null +++ b/libraries/tests/AWS.Lambda.PowerTools.Tracing.Tests/TracingAttributeTest.cs @@ -0,0 +1,625 @@ +using System; +using System.Linq; +using Amazon.Lambda.PowerTools.Tracing; +using Amazon.Lambda.PowerTools.Tracing.Internal; +using AWS.Lambda.PowerTools.Aspects; +using AWS.Lambda.PowerTools.Core; +using Moq; +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] + +namespace AWS.Lambda.PowerTools.Tracing.Tests +{ + [Collection("Sequential")] + public class TracingAttributeColdStartTest + { + [Fact] + public void OnEntry_WhenFirstCall_CapturesColdStart() + { + // Arrange + const bool isColdStart = true; + var methodName = Guid.NewGuid().ToString(); + var serviceName = Guid.NewGuid().ToString(); + + var configurations1 = new Mock(); + configurations1.Setup(c => + c.IsServiceNameDefined + ).Returns(true); + configurations1.Setup(c => + c.ServiceName + ).Returns(serviceName); + + var configurations2 = new Mock(); + configurations2.Setup(c => + c.IsServiceNameDefined + ).Returns(false); + + var recorder1 = new Mock(); + var recorder2 = new Mock(); + var recorder3 = new Mock(); + var recorder4 = new Mock(); + + var handler1 = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, + configurations1.Object, recorder1.Object); + var handler2 = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, + configurations1.Object, recorder2.Object); + var handler3 = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, + configurations2.Object, recorder3.Object); + var handler4 = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, + configurations2.Object, recorder4.Object); + + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + // Cold Start Execution + handler1.OnEntry(eventArgs); + handler2.OnEntry(eventArgs); + handler2.OnExit(eventArgs); + handler1.OnExit(eventArgs); + + // Warm Start Execution + handler3.OnEntry(eventArgs); + handler4.OnEntry(eventArgs); + handler4.OnExit(eventArgs); + handler3.OnExit(eventArgs); + + // Assert + recorder1.Verify(v => + v.AddAnnotation( + It.Is(i => i == "ColdStart"), + It.Is(i => i == isColdStart) + ), Times.Once); + + recorder1.Verify(v => + v.AddAnnotation( + It.Is(i => i == "Service"), + It.Is(i => i == serviceName) + ), Times.Once); + + recorder2.Verify(v => + v.AddAnnotation( + It.IsAny(), + It.IsAny() + ), Times.Never); + + recorder2.Verify(v => + v.AddAnnotation( + It.IsAny(), + It.IsAny() + ), Times.Never); + + recorder3.Verify(v => + v.AddAnnotation( + It.Is(i => i == "ColdStart"), + It.Is(i => i == !isColdStart) + ), Times.Once); + + recorder3.Verify(v => + v.AddAnnotation( + It.IsAny(), + It.IsAny() + ), Times.Never); + + recorder4.Verify(v => + v.AddAnnotation( + It.IsAny(), + It.IsAny() + ), Times.Never); + + recorder3.Verify(v => + v.AddAnnotation( + It.IsAny(), + It.IsAny() + ), Times.Never); + } + } + + [Collection("Sequential")] + public class TracingAttributeTest + { + #region OnEntry Tests + + [Fact] + public void OnEntry_WhenSegmentNameIsNull_BeginSubsegmentWithMethodName() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var configurations = new Mock(); + var recorder = new Mock(); + + var handler = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + handler.OnEntry(eventArgs); + + // Assert + recorder.Verify(v => + v.BeginSubsegment( + It.Is(i => i == $"## {methodName}"), + It.IsAny() + ), Times.Once); + } + + [Fact] + public void OnEntry_WhenSegmentNameHasValue_BeginSubsegmentWithValue() + { + // Arrange + var segmentName = Guid.NewGuid().ToString(); + var methodName = Guid.NewGuid().ToString(); + var configurations = new Mock(); + var recorder = new Mock(); + + var handler = new TracingAspectHandler(segmentName, null, TracingCaptureMode.EnvironmentVariable, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + handler.OnEntry(eventArgs); + + // Assert + recorder.Verify(v => + v.BeginSubsegment( + It.Is(i => i == segmentName), + It.IsAny() + ), Times.Once); + } + + [Fact] + public void OnEntry_WhenNamespaceIsNull_SetNamespaceWithServiceName() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var serviceName = Guid.NewGuid().ToString(); + var configurations = new Mock(); + configurations.Setup(c => + c.ServiceName + ).Returns(serviceName); + var recorder = new Mock(); + + var handler = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + handler.OnEntry(eventArgs); + + // Assert + recorder.Verify(v => + v.SetNamespace( + It.Is(i => i == serviceName) + ), Times.Once); + } + + [Fact] + public void OnEntry_WhenNamespaceHasValue_SetNamespaceWithValue() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var nameSpace = Guid.NewGuid().ToString(); + var configurations = new Mock(); + var recorder = new Mock(); + + var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.EnvironmentVariable, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + handler.OnEntry(eventArgs); + + // Assert + recorder.Verify(v => + v.SetNamespace( + It.Is(i => i == nameSpace) + ), Times.Once); + } + + #endregion + + #region OnSuccess Tests + + [Fact] + public void OnSuccess_WhenTracerCaptureResponseEnvironmentVariableIsTrue_CapturesResponse() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var nameSpace = Guid.NewGuid().ToString(); + var configurations = new Mock(); + configurations.Setup(c => + c.TracerCaptureResponse + ).Returns(true); + var recorder = new Mock(); + var results = new[] {"A", "B"}; + + var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.EnvironmentVariable, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + handler.OnSuccess(eventArgs, results); + + // Assert + recorder.Verify(v => + v.AddMetadata( + It.Is(i => i == nameSpace), + It.Is(i => i == $"{methodName} response"), + It.Is(i => + i.First() == results.First() && + i.Last() == results.Last() + ) + ), Times.Once); + } + + [Fact] + public void OnSuccess_WhenTracerCaptureResponseEnvironmentVariableIsFalse_DoesNotCaptureResponse() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var nameSpace = Guid.NewGuid().ToString(); + var configurations = new Mock(); + configurations.Setup(c => + c.TracerCaptureResponse + ).Returns(false); + var recorder = new Mock(); + var results = new[] {"A", "B"}; + + var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.EnvironmentVariable, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + handler.OnSuccess(eventArgs, results); + + // Assert + recorder.Verify(v => + v.AddMetadata( + It.IsAny(), + It.IsAny(), + It.IsAny() + ), Times.Never); + } + + [Fact] + public void OnSuccess_WhenTracerCaptureModeIsResponse_CapturesResponse() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var nameSpace = Guid.NewGuid().ToString(); + var configurations = new Mock(); + var recorder = new Mock(); + var results = new[] {"A", "B"}; + + var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.Response, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + handler.OnSuccess(eventArgs, results); + + // Assert + recorder.Verify(v => + v.AddMetadata( + It.Is(i => i == nameSpace), + It.Is(i => i == $"{methodName} response"), + It.Is(i => + i.First() == results.First() && + i.Last() == results.Last() + ) + ), Times.Once); + } + + [Fact] + public void OnSuccess_WhenTracerCaptureModeIsResponseAndError_CapturesResponse() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var nameSpace = Guid.NewGuid().ToString(); + var configurations = new Mock(); + var recorder = new Mock(); + var results = new[] {"A", "B"}; + + var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.ResponseAndError, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + handler.OnSuccess(eventArgs, results); + + // Assert + recorder.Verify(v => + v.AddMetadata( + It.Is(i => i == nameSpace), + It.Is(i => i == $"{methodName} response"), + It.Is(i => + i.First() == results.First() && + i.Last() == results.Last() + ) + ), Times.Once); + } + + [Fact] + public void OnSuccess_WhenTracerCaptureModeIsError_DoesNotCaptureResponse() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var nameSpace = Guid.NewGuid().ToString(); + var configurations = new Mock(); + var recorder = new Mock(); + var results = new[] {"A", "B"}; + + var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.Error, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + handler.OnSuccess(eventArgs, results); + + // Assert + recorder.Verify(v => + v.AddMetadata( + It.IsAny(), + It.IsAny(), + It.IsAny() + ), Times.Never); + } + + [Fact] + public void OnSuccess_WhenTracerCaptureModeIsDisabled_DoesNotCaptureResponse() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var nameSpace = Guid.NewGuid().ToString(); + var configurations = new Mock(); + var recorder = new Mock(); + var results = new[] {"A", "B"}; + + var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.Disabled, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + handler.OnSuccess(eventArgs, results); + + // Assert + recorder.Verify(v => + v.AddMetadata( + It.IsAny(), + It.IsAny(), + It.IsAny() + ), Times.Never); + } + + #endregion + + #region OnException Tests + + [Fact] + public void OnException_WhenTracerCaptureErrorEnvironmentVariableIsTrue_CapturesError() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var nameSpace = Guid.NewGuid().ToString(); + var configurations = new Mock(); + configurations.Setup(c => + c.TracerCaptureError + ).Returns(true); + var recorder = new Mock(); + var exception = new Exception("Test Exception"); + + var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.EnvironmentVariable, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + object Act() => handler.OnException(eventArgs, exception); + + // Assert + Assert.Throws(Act); + recorder.Verify(v => + v.AddMetadata( + It.Is(i => i == nameSpace), + It.Is(i => i == $"{methodName} error"), + It.Is(i => i == exception + ) + ), Times.Once); + } + + [Fact] + public void OnException_WhenTracerCaptureErrorEnvironmentVariableIsTrueFalse_DoesNotCaptureError() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var nameSpace = Guid.NewGuid().ToString(); + var configurations = new Mock(); + configurations.Setup(c => + c.TracerCaptureError + ).Returns(false); + var recorder = new Mock(); + var exception = new Exception("Test Exception"); + + var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.EnvironmentVariable, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + object Act() => handler.OnException(eventArgs, exception); + + // Assert + Assert.Throws(Act); + recorder.Verify(v => + v.AddMetadata( + It.IsAny(), + It.IsAny(), + It.IsAny() + ), Times.Never); + } + + [Fact] + public void OnException_WhenTracerCaptureModeIsError_CapturesError() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var nameSpace = Guid.NewGuid().ToString(); + var configurations = new Mock(); + var recorder = new Mock(); + var exception = new Exception("Test Exception"); + + var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.Error, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + object Act() => handler.OnException(eventArgs, exception); + + // Assert + Assert.Throws(Act); + recorder.Verify(v => + v.AddMetadata( + It.Is(i => i == nameSpace), + It.Is(i => i == $"{methodName} error"), + It.Is(i => i == exception + ) + ), Times.Once); + } + + [Fact] + public void OnException_WhenTracerCaptureModeIsResponseAndError_CapturesError() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var nameSpace = Guid.NewGuid().ToString(); + var configurations = new Mock(); + var recorder = new Mock(); + var exception = new Exception("Test Exception"); + + var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.ResponseAndError, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + object Act() => handler.OnException(eventArgs, exception); + + // Assert + Assert.Throws(Act); + recorder.Verify(v => + v.AddMetadata( + It.Is(i => i == nameSpace), + It.Is(i => i == $"{methodName} error"), + It.Is(i => i == exception + ) + ), Times.Once); + } + + [Fact] + public void OnException_WhenTracerCaptureModeIsResponse_DoesNotCaptureError() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var nameSpace = Guid.NewGuid().ToString(); + var configurations = new Mock(); + configurations.Setup(c => + c.TracerCaptureError + ).Returns(false); + var recorder = new Mock(); + var exception = new Exception("Test Exception"); + + var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.Response, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + object Act() => handler.OnException(eventArgs, exception); + + // Assert + Assert.Throws(Act); + recorder.Verify(v => + v.AddMetadata( + It.IsAny(), + It.IsAny(), + It.IsAny() + ), Times.Never); + } + + [Fact] + public void OnException_WhenTracerCaptureModeIsDisabled_DoesNotCaptureError() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var nameSpace = Guid.NewGuid().ToString(); + var configurations = new Mock(); + configurations.Setup(c => + c.TracerCaptureError + ).Returns(false); + var recorder = new Mock(); + var exception = new Exception("Test Exception"); + + var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.Disabled, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + object Act() => handler.OnException(eventArgs, exception); + + // Assert + Assert.Throws(Act); + recorder.Verify(v => + v.AddMetadata( + It.IsAny(), + It.IsAny(), + It.IsAny() + ), Times.Never); + } + + #endregion + + #region OnExit Tests + + [Fact] + public void OnExit_WhenIsNotSamLocal_EndSubsegment() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var configurations = new Mock(); + configurations.Setup(c => + c.IsSamLocal + ).Returns(false); + var recorder = new Mock(); + + var handler = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + handler.OnExit(eventArgs); + + // Assert + recorder.Verify(v => v.EndSubsegment(), Times.Once); + } + + [Fact] + public void OnExit_WhenIsSamLocal_DoesNotEndSubsegment() + { + // Arrange + var methodName = Guid.NewGuid().ToString(); + var configurations = new Mock(); + configurations.Setup(c => + c.IsSamLocal + ).Returns(true); + var recorder = new Mock(); + + var handler = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, + configurations.Object, recorder.Object); + var eventArgs = new AspectEventArgs {Name = methodName}; + + // Act + handler.OnExit(eventArgs); + + // Assert + recorder.Verify(v => v.EndSubsegment(), Times.Never); + } + + #endregion + } +} \ No newline at end of file