Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
languagec#
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Connexion.Core;
using Connexion.Share.Threading;

namespace Connexion.Device
{
  public class CustomAuditing : BaseCustomAuditing
  {
    /// <summary>
    /// This class allows you to provide custom workflow for your audit data. For example, you may wish to save your audit data into specific files, or, send your audit data to a
    /// service over the network. Use the Start() and Stop() methods to initialize and tear down class-level variables. Use the ProcessAuditRecordsAsync method to process non-phi
    /// audit data, and the ProcessPhiAuditRecordAsync method to process phi data.
    /// ***
    /// *** The ProcessAuditRecordsAsync and ProcessPhiAuditRecordAsync methods run in separate threads, which means you *must synchronize* these methods if you are writing to a
    /// *** single non-thread-safe resource (such as a file). Use of lock(...) is acceptable.
    /// ***
    /// If you only wish to copy the raw audit data to another location (and skip processing), you can use the reader.FilePath property. In this case you would need to deploy your
    /// own audit parsing and processing application.
    /// ***
    /// The below sample code is a non-production sample.
    /// </summary>
    
    private StreamWriter m_Writer;
    private readonly AsyncLockEx m_AsyncLock = new AsyncLockEx();
    
    public override void Start()
    {
      // initialize any class-level resources
    }
    
    public override void Stop()
    {
      // tear-down any class-level resources
      CloseStream();
    }
    
    private void CloseStream()
    {
      var temp = m_Writer;
       m_Writer = null;
       temp?.BaseStream?.Close();
    }
    
    private void CreateStream()
    {
       if(m_Writer?.BaseStream?.Length < 10 * 1024 * 1024)
         return;
     
       CloseStream();
       
       var targetPath = "c:\\temp\\audit\\";
       Directory.CreateDirectory(targetPath);
       
       var filePath = $"{targetPath}audit_{DateTime.Now.ToString("yyyyMMddHHmmss")}.txt";
       var stream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
       m_Writer = new StreamWriter(stream, Encoding.UTF8, 8 * 1024, false);
    }
    
    public override async Task ProcessAuditRecordAsync(IAuditReader<AuditRecord> reader, CancellationToken cancellationToken)
    {
      // Non-PHI data (your logic here, do something better than below)
      foreach(var batch in reader.EnumerateBatches(100))
      {
        var builder = new StringBuilder();
        foreach(var record in batch)
        {
          builder.AppendLine(record.ToString());
        }
        await WriteToAuditRecord(builder.ToString());
      }
    }
    
    public override async Task ProcessPhiAuditRecordAsync(IAuditReader<PhiAuditRecord> reader, CancellationToken cancellationToken)
    {
      // Phi data (your logic here, do something better than below)
      foreach(var batch in reader.EnumerateBatches(100))
      {
        var builder = new StringBuilder();
        foreach(var record in batch)
        {
          builder.Append(record.ToStringWithPhi());
        }
        await WriteToAuditRecord(builder.ToString());
      }
    }
    
    private async Task WriteToAuditRecord(string toWrite)
    {
      using(await m_AsyncLock.LockAsync())
      {
        CreateStream();
        await m_Writer.WriteAsync(toWrite);
        await m_Writer.FlushAsync();
      }
    }
  }
}

Device/Plugin developers can write audit events from within their own code by grabbing a handle to an AuditProvider. Assuming you have a message context, you can use the snippet below to write a record which will then be processed by the code above.

Code Block
if (MessageChannel.AuditProvider?.IsPhiMessageFlowAuditingEnabled == true)
{
  var ar = context.CreatePhiAuditRecord(AuditAction.Device.SendMessage);
  ar.TargetUri = m_Client.RemoteEndPoint.ToString();
  MessageChannel.AuditProvider.AuditPhi(ar);
}