Skip to content

Commit

Permalink
Support to include the Activity distributed tracing information into …
Browse files Browse the repository at this point in the history
…the Audit Event. Adding Configuration.Reset() method to reset the global settings to the default.
  • Loading branch information
Federico Colombo authored and Federico Colombo committed Dec 1, 2023
1 parent c00949f commit a293e25
Show file tree
Hide file tree
Showing 17 changed files with 430 additions and 60 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ All notable changes to Audit.NET and its extensions will be documented in this f

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

## [22.0.2] - 2023-12-01:
- Audit.NET: Support to include the [Activity](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing-instrumentation-walkthroughs) distributed trace information into the Audit Event.
- Audit.NET: Adding `Configuration.Reset()` method to reset the global settings to the default.

## [22.0.1] - 2023-11-15:
- Audit.EntityFramework.Core: Adding support for [Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types) in EF Core 8.
Complex types are now included in the `ColumnValues` and `Changes` collections of the EF audit entity.
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>22.0.1</Version>
<Version>22.0.2</Version>
<PackageReleaseNotes></PackageReleaseNotes>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
</PropertyGroup>
Expand Down
44 changes: 39 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,15 @@ An example of the output in JSON:
"Exception": null,
"Culture": "en-GB"
},
"StartDate": "2016-08-23T11:33:14.653191-05:00",
"EndDate": "2016-08-23T11:33:23.1820786-05:00",
"Activity": {
"StartTimeUtc": "2023-12-01T17:36:52.2256288Z",
"SpanId": "23a93b9e8cbc457f",
"TraceId": "2d3e5e90f790c7d2274d9bb047531f66",
"ParentId": "0000000000000000",
"Operation": "Update"
},
"StartDate": "2016-08-23T11:33:14.653191Z",
"EndDate": "2016-08-23T11:33:23.1820786Z",
"Duration": 8529,
"Target": {
"Type": "Order",
Expand Down Expand Up @@ -685,14 +692,39 @@ The `ActionType` indicates when to perform the action. The allowed values are:
- `OnEventSaving`: When an Audit Scope's Event is about to be saved.
- `OnEventSaved`: After an Audit Scope's Event is saved.

### IncludeStackTrace
### Stack Trace

Include the stack trace in the event. Default is `false`.
To include the stack trace details into the event environment, ensure that the `IncludeStackTrace` configuration is set to `true`. Default is `false`.

```c#
Audit.Core.Configuration.IncludeStackTrace = true;
```

or

```c#
Audit.Core.Configuration.Setup()
.IncludeStackTrace();
```

### Activity Trace

To include the activy trace details from [System.Diagnostics.Activity](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activity?view=net-8.0)
API into the event, ensure that the `IncludeActivityTrace` configuration is set to `true`. Default is `false`.

It will include the current `Activity` operation name, ID, StartTime, along with associated Tags and Events.

```c#
Audit.Core.Configuration.IncludeActivityTrace = true;
```

or

```c#
Audit.Core.Configuration.Setup()
.IncludeActivityTrace();
```

### Global switch off

You can disable audit logging by setting the static property `Configuration.AuditDisabled` to `true`.
Expand Down Expand Up @@ -817,7 +849,9 @@ Alternatively to the properties/methods mentioned before, you can configure the
For example, to set the FileLog Provider with its default settings using a Manual creation policy:
```c#
Audit.Core.Configuration.Setup()
Audit.Core.Configuration.Setup
.IncludeStackTrace()
.IncludeActivityTrace()
.UseFileLogProvider()
.WithCreationPolicy(EventCreationPolicy.Manual);
```
Expand Down
80 changes: 80 additions & 0 deletions src/Audit.NET/AuditActivityTrace.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#if NET5_0_OR_GREATER
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Audit.Core
{
public class AuditActivityTrace : IAuditOutput
{
/// <summary>
/// Date and time when the Activity started
/// </summary>
public DateTime StartTimeUtc { get; set; }

/// <summary>
/// SPAN part of the Id
/// </summary>
public string SpanId { get; set; }

/// <summary>
/// TraceId part of the Id
/// </summary>
public string TraceId { get; set; }

/// <summary>
/// Id of the activity's parent
/// </summary>
public string ParentId { get; set; }

/// <summary>
/// Operation name
/// </summary>
public string Operation { get; set; }

/// <summary>
/// List of tags (key/value pairs) associated to the activity
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<AuditActivityTag> Tags { get; set; }

/// <summary>
/// List of events (timestamped messages) attached to the activity
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<AuditActivityEvent> Events { get; set; }

[JsonExtensionData]
public Dictionary<string, object> CustomFields { get; set; }

/// <summary>
/// Serializes this Activity Info entity as a JSON string
/// </summary>
public string ToJson()
{
return Configuration.JsonAdapter.Serialize(this);
}

/// <summary>
/// Parses an AuditActivityInfo entity from its JSON string representation.
/// </summary>
/// <param name="json">JSON string with the AuditActivityInfo entity representation.</param>
public static AuditActivityTrace FromJson(string json)
{
return Configuration.JsonAdapter.Deserialize<AuditActivityTrace>(json);
}
}

public class AuditActivityTag
{
public string Key { get; set; }
public object Value { get; set; }
}

public class AuditActivityEvent
{
public DateTimeOffset Timestamp { get; set; }
public string Name { get; set; }
}
}
#endif
7 changes: 7 additions & 0 deletions src/Audit.NET/AuditEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ public class AuditEvent : IAuditOutput
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
#endif
public AuditEventEnvironment Environment { get; set; }

#if NET5_0_OR_GREATER
/// <summary>
/// The current distributed tracing activity information
/// </summary>
public AuditActivityTrace Activity { get; set; }
#endif

/// <summary>
/// The extension data.
Expand Down
127 changes: 104 additions & 23 deletions src/Audit.NET/AuditScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading.Tasks;
using System.Diagnostics;
using System.Threading;
using System.Linq;

namespace Audit.Core
{
Expand All @@ -24,6 +25,104 @@ internal AuditScope(AuditScopeOptions options)
_creationPolicy = options.CreationPolicy ?? Configuration.CreationPolicy;
_dataProvider = options.DataProvider ?? Configuration.DataProvider;
_targetGetter = options.TargetGetter;

_event = options.AuditEvent ?? new AuditEvent();

_event.Environment = GetEnvironmentInfo(options);

#if NET5_0_OR_GREATER
if (options.IncludeActivityTrace)
{
_event.Activity = GetActivityTrace();
}
#endif
_event.StartDate = Configuration.SystemClock.UtcNow;
if (options.EventType != null)
{
_event.EventType = options.EventType;
}
if (_event.CustomFields == null)
{
_event.CustomFields = new Dictionary<string, object>();
}
if (options.TargetGetter != null)
{
var targetValue = options.TargetGetter.Invoke();
_event.Target = new AuditTarget
{
Old = _dataProvider.Serialize(targetValue),
Type = targetValue?.GetType().GetFullTypeName() ?? "Object"
};
}
ProcessExtraFields(options.ExtraFields);
}

#if NET5_0_OR_GREATER
private AuditActivityTrace GetActivityTrace()
{
var activity = Activity.Current;

if (activity == null)
{
return null;
}

var spanId = activity.IdFormat switch
{
ActivityIdFormat.Hierarchical => activity.Id,
ActivityIdFormat.W3C => activity.SpanId.ToHexString(),
_ => null
};

var traceId = activity.IdFormat switch
{
ActivityIdFormat.Hierarchical => activity.RootId,
ActivityIdFormat.W3C => activity.TraceId.ToHexString(),
_ => null
};

var parentId = activity.IdFormat switch
{
ActivityIdFormat.Hierarchical => activity.ParentId,
ActivityIdFormat.W3C => activity.ParentSpanId.ToHexString(),
_ => null
};


var result = new AuditActivityTrace()
{
StartTimeUtc = activity.StartTimeUtc,
SpanId = spanId,
TraceId = traceId,
ParentId = parentId,
Operation = activity.OperationName
};

if (activity.Tags.Any())
{
result.Tags = new List<AuditActivityTag>();
foreach (var tag in activity.Tags)
{
result.Tags.Add(new AuditActivityTag() { Key = tag.Key, Value = tag.Value });
}
}

if (activity.Events.Any())
{
result.Events = new List<AuditActivityEvent>();
foreach (var ev in activity.Events)
{
result.Events.Add(new AuditActivityEvent() { Timestamp = ev.Timestamp, Name = ev.Name });
}
}

return result;
}
#endif

[MethodImpl(MethodImplOptions.NoInlining)]
private AuditEventEnvironment GetEnvironmentInfo(AuditScopeOptions options)
{
var environment = new AuditEventEnvironment()
{
Culture = System.Globalization.CultureInfo.CurrentCulture.ToString(),
Expand All @@ -35,7 +134,7 @@ internal AuditScope(AuditScopeOptions options)
environment.DomainName = Environment.UserDomainName;
if (callingMethod == null)
{
callingMethod = new StackFrame(2 + options.SkipExtraFrames).GetMethod();
callingMethod = new StackFrame(3 + options.SkipExtraFrames).GetMethod();
}
if (options.IncludeStackTrace)
{
Expand All @@ -50,28 +149,10 @@ internal AuditScope(AuditScopeOptions options)
environment.CallingMethodName = (callingMethod.DeclaringType != null ? callingMethod.DeclaringType.FullName + "." : "") + callingMethod.Name + "()";
environment.AssemblyName = callingMethod.DeclaringType?.GetTypeInfo().Assembly.FullName;
}
_event = options.AuditEvent ?? new AuditEvent();
_event.Environment = environment;
_event.StartDate = Configuration.SystemClock.UtcNow;
if (options.EventType != null)
{
_event.EventType = options.EventType;
}
if (_event.CustomFields == null)
{
_event.CustomFields = new Dictionary<string, object>();
}
if (options.TargetGetter != null)
{
var targetValue = options.TargetGetter.Invoke();
_event.Target = new AuditTarget
{
Old = _dataProvider.Serialize(targetValue),
Type = targetValue?.GetType().GetFullTypeName() ?? "Object"
};
}
ProcessExtraFields(options.ExtraFields);

return environment;
}

/// <summary>
/// Starts an audit scope
/// </summary>
Expand Down Expand Up @@ -138,7 +219,7 @@ internal async Task<AuditScope> StartAsync(CancellationToken cancellationToken =
}
return this;
}
#endregion
#endregion

#region Public Properties
/// <summary>
Expand Down
Loading

0 comments on commit a293e25

Please sign in to comment.